From 3c5323bcf564da06838acf34b77d88c82005e83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Domen=20Ko=C5=BEar?= Date: Sun, 20 Sep 2026 14:22:02 -0500 Subject: [PATCH] service/tray: forward Wayland activation tokens on primary clicks --- changelog/next.md | 4 + src/services/status_notifier/CMakeLists.txt | 7 ++ src/services/status_notifier/item.cpp | 38 ++++++++ src/services/status_notifier/item.hpp | 2 + .../org.kde.StatusNotifierItem.xml | 3 + src/wayland/CMakeLists.txt | 10 +++ src/wayland/activation.cpp | 89 +++++++++++++++++++ src/wayland/activation.hpp | 20 +++++ src/wayland/wlr_layershell/CMakeLists.txt | 2 +- src/wayland/wlr_layershell/surface.cpp | 5 ++ src/wayland/wlr_layershell/surface.hpp | 2 + 11 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/wayland/activation.cpp create mode 100644 src/wayland/activation.hpp diff --git a/changelog/next.md b/changelog/next.md index 13cdee2..83c1ae0 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -1,3 +1,7 @@ +## Other Changes + +- `SystemTrayItem.activate()` now forwards Wayland activation tokens to applications. + ## Bug Fixes - Fixed ScriptModel not sending role updates on changed data that compares equal by key or structure. diff --git a/src/services/status_notifier/CMakeLists.txt b/src/services/status_notifier/CMakeLists.txt index 913152e..484a21d 100644 --- a/src/services/status_notifier/CMakeLists.txt +++ b/src/services/status_notifier/CMakeLists.txt @@ -52,3 +52,10 @@ target_link_libraries(quickshell-service-statusnotifier PRIVATE Qt::Quick Qt::DB target_link_libraries(quickshell PRIVATE quickshell-service-statusnotifierplugin) qs_module_pch(quickshell-service-statusnotifier SET dbus) + +if (WAYLAND) + target_compile_definitions(quickshell-service-statusnotifier PRIVATE QS_WAYLAND) + target_link_libraries(quickshell-service-statusnotifier PRIVATE + quickshell-wayland-activation + ) +endif() diff --git a/src/services/status_notifier/item.cpp b/src/services/status_notifier/item.cpp index 67bde01..3a4520c 100644 --- a/src/services/status_notifier/item.cpp +++ b/src/services/status_notifier/item.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,10 @@ #include "host.hpp" #include "watcher.hpp" +#ifdef QS_WAYLAND +#include "../../wayland/activation.hpp" +#endif + using namespace qs::dbus; using namespace qs::dbus::dbusmenu; using namespace qs::menu::platform; @@ -219,6 +224,39 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const { } void StatusNotifierItem::activate() { +#ifdef QS_WAYLAND + auto callback = [item = QPointer(this)](const QString& token) { + if (item) item->activateWithToken(token); + }; + + if (qs::wayland::activation::requestActivationToken(callback)) return; +#endif + this->activateWithoutToken(); +} + +void StatusNotifierItem::activateWithToken(const QString& token) { + if (token.isEmpty()) { + this->activateWithoutToken(); + return; + } + + auto* call = new QDBusPendingCallWatcher(this->item->ProvideXdgActivationToken(token), this); + QObject::connect( + call, + &QDBusPendingCallWatcher::finished, + this, + [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply<> reply = *call; + if (reply.isError() && reply.error().type() != QDBusError::UnknownMethod) { + qCWarning(logStatusNotifierItem) << "Error providing activation token:" << reply.error(); + } + delete call; + this->activateWithoutToken(); + } + ); +} + +void StatusNotifierItem::activateWithoutToken() { auto pendingCall = this->item->Activate(0, 0); auto* call = new QDBusPendingCallWatcher(pendingCall, this); diff --git a/src/services/status_notifier/item.hpp b/src/services/status_notifier/item.hpp index bd8fe8c..6caee54 100644 --- a/src/services/status_notifier/item.hpp +++ b/src/services/status_notifier/item.hpp @@ -169,6 +169,8 @@ private slots: void onGetAllFailed() const; private: + void activateWithToken(const QString& token); + void activateWithoutToken(); void updateMenuState(); void updatePixmapIndex(); void onMenuPathChanged(); diff --git a/src/services/status_notifier/org.kde.StatusNotifierItem.xml b/src/services/status_notifier/org.kde.StatusNotifierItem.xml index 0aa43c7..0cd3b8a 100644 --- a/src/services/status_notifier/org.kde.StatusNotifierItem.xml +++ b/src/services/status_notifier/org.kde.StatusNotifierItem.xml @@ -29,6 +29,9 @@ + + + diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 1768186..cc19b14 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -73,6 +73,16 @@ endfunction() # ----- +wl_proto(wlp-xdg-activation xdg-activation-v1 "${WAYLAND_PROTOCOLS}/staging/xdg-activation") + +qt_add_library(quickshell-wayland-activation STATIC + activation.cpp +) + +target_link_libraries(quickshell-wayland-activation PRIVATE + Qt::Gui Qt::WaylandClient Qt::WaylandClientPrivate wlp-xdg-activation +) + qt_add_library(quickshell-wayland STATIC wl_proxy_safe_deref.cpp platformmenu.cpp diff --git a/src/wayland/activation.cpp b/src/wayland/activation.cpp new file mode 100644 index 0000000..01369d2 --- /dev/null +++ b/src/wayland/activation.cpp @@ -0,0 +1,89 @@ +#include "activation.hpp" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wayland-xdg-activation-v1-client-protocol.h" + +namespace qs::wayland::activation { +namespace { + +class ActivationManager + : public QWaylandClientExtensionTemplate + , public QtWayland::xdg_activation_v1 { +public: + ActivationManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); } +}; + +class ActivationToken + : public QObject + , public QtWayland::xdg_activation_token_v1 { +public: + ActivationToken(::xdg_activation_token_v1* token, QtWaylandClient::QWaylandWindow* window) + : QObject(window) + , QtWayland::xdg_activation_token_v1(token) + , window(window) {} + + ~ActivationToken() override { this->destroy(); } + Q_DISABLE_COPY_MOVE(ActivationToken); + +private: + void xdg_activation_token_v1_done(const QString& token) override { + this->deleteLater(); + if (this->window) emit this->window->xdgActivationTokenCreated(token); + } + + QPointer window; +}; + +} // namespace + +bool requestActivationToken(std::function callback) { + if (QGuiApplication::platformName() != "wayland") return false; + + auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); + auto* window = display->lastInputWindow(); + if (!window || !window->surface() || !display->lastInputDevice()) return false; + + QObject::connect( + window, + &QtWaylandClient::QWaylandWindow::xdgActivationTokenCreated, + window, + std::move(callback), + Qt::SingleShotConnection + ); + window->requestXdgActivationToken(display->lastInputSerial()); + return true; +} + +void requestXdgActivationToken(QtWaylandClient::QWaylandWindow* window, quint32 serial) { + auto* display = window->display(); + auto* seat = display->lastInputDevice(); + + static auto* manager = new ActivationManager(); // NOLINT + if (!manager->isActive() || !window->surface() || !seat) { + emit window->xdgActivationTokenCreated({}); + return; + } + + auto* token = new ActivationToken(manager->get_activation_token(), window); + token->set_serial(serial, seat->object()); + token->set_surface(window->surface()); + token->commit(); +} + +} // namespace qs::wayland::activation diff --git a/src/wayland/activation.hpp b/src/wayland/activation.hpp new file mode 100644 index 0000000..85893ae --- /dev/null +++ b/src/wayland/activation.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +namespace QtWaylandClient { // NOLINT(readability-identifier-naming) +class QWaylandWindow; +} + +namespace qs::wayland::activation { + +// Request a token using Qt's most recent input event. Returns false if unable to request a token. +bool requestActivationToken(std::function callback); + +// Request an activation token for a window to be delivered via QWaylandWindow::xdgActivationTokenCreated +void requestXdgActivationToken(QtWaylandClient::QWaylandWindow* window, quint32 serial); + +} // namespace qs::wayland::activation diff --git a/src/wayland/wlr_layershell/CMakeLists.txt b/src/wayland/wlr_layershell/CMakeLists.txt index 752c2cb..d25b9e0 100644 --- a/src/wayland/wlr_layershell/CMakeLists.txt +++ b/src/wayland/wlr_layershell/CMakeLists.txt @@ -24,7 +24,7 @@ wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell") target_link_libraries(quickshell-wayland-layershell PRIVATE Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-layer-shell wlp-xdg-shell + wlp-layer-shell wlp-xdg-shell quickshell-wayland-activation ) qs_module_pch(quickshell-wayland-layershell SET large) diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp index 0b0e7d7..e260f6f 100644 --- a/src/wayland/wlr_layershell/surface.cpp +++ b/src/wayland/wlr_layershell/surface.cpp @@ -20,6 +20,7 @@ #include #include "../../window/panelinterface.hpp" +#include "../activation.hpp" #include "shell_integration.hpp" #include "wlr_layershell.hpp" @@ -130,6 +131,10 @@ LayerSurfaceBridge* LayerSurfaceBridge::init(QWindow* window, LayerSurfaceState return bridge; } +void LayerSurface::requestXdgActivationToken(quint32 serial) { + activation::requestXdgActivationToken(this->window(), serial); +} + LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWaylandWindow* window) : QtWaylandClient::QWaylandShellSurface(window) { diff --git a/src/wayland/wlr_layershell/surface.hpp b/src/wayland/wlr_layershell/surface.hpp index d782d71..aef2b72 100644 --- a/src/wayland/wlr_layershell/surface.hpp +++ b/src/wayland/wlr_layershell/surface.hpp @@ -66,6 +66,8 @@ public: void applyConfigure() override; void setWindowGeometry(const QRect& /*geometry*/) override {} + void requestXdgActivationToken(quint32 serial) override; + void attachPopup(QtWaylandClient::QWaylandShellSurface* popup) override; void commit();