From fee4de7cb3e097f4515b684a1df52799af6b3578 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Mon, 8 Jun 2026 14:28:44 +0800 Subject: [PATCH] feat: implement xdg-activation-v1 protocol for tray protocol handler Implement xdg-activation-v1 protocol support in the system tray protocol handler to provide proper XDG activation tokens for application window management, aligned with dde-shell implementation. This change moves the XdgActivation class to the dockpluginmanager-interface library for shared use across dock components. Key changes: 1. Add XdgActivation class to dockpluginmanager-interface library 2. Implement xdg-activation-v1 Wayland protocol client integration 3. Update tray protocol handler to use XDG activation token when activating SNI items 4. Add ProvideXdgActivationToken method to SNI DBus interface 5. Update library dependencies and CMake build configuration Log: Implemented XDG activation protocol for proper window focus management Co-Authored-By: Claude Opus 4.8 --- CMakeLists.txt | 5 +- .../api/dbus/org.kde.StatusNotifierItem.xml | 3 + .../application-tray/sniprotocolhandler.cpp | 23 ++- src/tray-wayland-integration/CMakeLists.txt | 23 ++- .../xdgactivation.cpp | 173 ++++++++++++++++++ src/tray-wayland-integration/xdgactivation.h | 30 +++ .../xdgactivation_p.h | 28 +++ 7 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 src/tray-wayland-integration/xdgactivation.cpp create mode 100644 src/tray-wayland-integration/xdgactivation.h create mode 100644 src/tray-wayland-integration/xdgactivation_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bd8e8da0..d5224b708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: CC0-1.0 @@ -40,6 +40,9 @@ endif() include(GNUInstallDirs) include(CMakePackageConfigHelpers) +find_package(PkgConfig REQUIRED) +pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir) + add_subdirectory(plugins) add_subdirectory(src) diff --git a/plugins/application-tray/api/dbus/org.kde.StatusNotifierItem.xml b/plugins/application-tray/api/dbus/org.kde.StatusNotifierItem.xml index f1858b46e..64e9b4dc1 100644 --- a/plugins/application-tray/api/dbus/org.kde.StatusNotifierItem.xml +++ b/plugins/application-tray/api/dbus/org.kde.StatusNotifierItem.xml @@ -41,6 +41,9 @@ + + + diff --git a/plugins/application-tray/sniprotocolhandler.cpp b/plugins/application-tray/sniprotocolhandler.cpp index 94a0ecbbf..79610539f 100644 --- a/plugins/application-tray/sniprotocolhandler.cpp +++ b/plugins/application-tray/sniprotocolhandler.cpp @@ -8,6 +8,7 @@ #include "util.h" #include "plugin.h" +#include "xdgactivation.h" #include "dbusmenuimporter.h" @@ -317,7 +318,27 @@ bool SniTrayProtocolHandler::eventFilter(QObject *watched, QEvent *event) if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent* mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { - m_sniInter->Activate(0, 0); + auto *activation = new XdgActivation(this); + if (activation->isActive()) { + auto *win = window()->windowHandle(); + if (!win) { + activation->deleteLater(); + return false; + } + + auto sniInter = m_sniInter; + connect(activation, &XdgActivation::tokenReady, this, [sniInter, activation](const QString &token) { + if (!token.isEmpty()) { + sniInter->ProvideXdgActivationToken(token); + } + sniInter->Activate(0, 0); + activation->deleteLater(); + }, Qt::SingleShotConnection); + activation->requestToken(win); + } else { + m_sniInter->Activate(0, 0); + activation->deleteLater(); + } } else if (mouseEvent->button() == Qt::RightButton) { if (!menuImporter()) { m_sniInter->ContextMenu(0, 0); diff --git a/src/tray-wayland-integration/CMakeLists.txt b/src/tray-wayland-integration/CMakeLists.txt index 9e17ca561..8d5220ec7 100644 --- a/src/tray-wayland-integration/CMakeLists.txt +++ b/src/tray-wayland-integration/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: CC0-1.0 @@ -6,7 +6,7 @@ find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Co if(Qt${QT_VERSION_MAJOR}_VERSION VERSION_GREATER_EQUAL 6.10) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS GuiPrivate WaylandClientPrivate REQUIRED) endif() -find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widget) +find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widget) find_package(ECM REQUIRED MO_MODULE) set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${ECM_MODULE_PATH}") @@ -17,6 +17,15 @@ include(KDEInstallDirs) add_library(dockpluginmanager-interface SHARED plugin.h plugin.cpp + xdgactivation.h + xdgactivation_p.h + xdgactivation.cpp +) + +qt_generate_wayland_protocol_client_sources(dockpluginmanager-interface +NO_INCLUDE_CORE_ONLY +FILES + ${WAYLAND_PROTOCOLS_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml ) target_include_directories(dockpluginmanager-interface PUBLIC @@ -28,6 +37,14 @@ PUBLIC Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Dtk${DTK_VERSION_MAJOR}::Widget +PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::GuiPrivate + Qt${QT_VERSION_MAJOR}::WaylandClient + Qt${QT_VERSION_MAJOR}::WaylandClientPrivate + Dtk${DTK_VERSION_MAJOR}::Core + Dtk${DTK_VERSION_MAJOR}::Gui + Wayland::Client ) add_library(dockpluginmanager SHARED @@ -53,6 +70,8 @@ PRIVATE Qt${QT_VERSION_MAJOR}::GuiPrivate Qt${QT_VERSION_MAJOR}::WaylandClient Qt${QT_VERSION_MAJOR}::WaylandClientPrivate + Dtk${DTK_VERSION_MAJOR}::Core + Dtk${DTK_VERSION_MAJOR}::Gui Wayland::Client ) diff --git a/src/tray-wayland-integration/xdgactivation.cpp b/src/tray-wayland-integration/xdgactivation.cpp new file mode 100644 index 000000000..e7fa65fbb --- /dev/null +++ b/src/tray-wayland-integration/xdgactivation.cpp @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "xdgactivation_p.h" + +#include +#include +#include +#include +#include +#include + +#include "qwayland-xdg-activation-v1.h" +#include +#include +#include + +Q_LOGGING_CATEGORY(trayXdgActivation, "dde.tray.xdgactivation") + +namespace tray { + +class XdgActivationTokenV1 : public QObject, public QtWayland::xdg_activation_token_v1 +{ + Q_OBJECT + +public: + ~XdgActivationTokenV1() override + { + destroy(); + } + +Q_SIGNALS: + void done(const QString &token); + +protected: + void xdg_activation_token_v1_done(const QString &token) override + { + Q_EMIT done(token); + } +}; + +namespace { + +class XdgActivationV1 : public QWaylandClientExtensionTemplate, + public QtWayland::xdg_activation_v1 +{ +public: + XdgActivationV1() + : QWaylandClientExtensionTemplate(1) + { + initialize(); + } + + ~XdgActivationV1() override + { + if (isInitialized()) + destroy(); + } + + XdgActivationTokenV1 *createTokenProvider(QWindow *window, const QString &appId) + { + auto *provider = new XdgActivationTokenV1; + provider->init(get_activation_token()); + + if (window) { + if (auto *waylandWindow = dynamic_cast(window->handle())) { + if (auto *surface = waylandWindow->wlSurface()) + provider->set_surface(surface); + if (auto *inputDevice = waylandWindow->display()->lastInputDevice()) + provider->set_serial(inputDevice->serial(), inputDevice->wl_seat()); + } + } + + if (!appId.isEmpty()) + provider->set_app_id(appId); + + provider->commit(); + return provider; + } +}; + +XdgActivationV1 *activationV1() +{ + static QPointer activation; + if (activation) + return activation; + + activation = new XdgActivationV1; + activation->setParent(qApp); + return activation; +} + +} // namespace + +// --------------------------------------------------------------------------- +// XdgActivationPrivate +// --------------------------------------------------------------------------- + +XdgActivationPrivate::XdgActivationPrivate(XdgActivation *qq) + : DObjectPrivate(qq) +{ +} + +XdgActivationPrivate::~XdgActivationPrivate() = default; + +// --------------------------------------------------------------------------- +// XdgActivation +// --------------------------------------------------------------------------- + +XdgActivation::XdgActivation(QObject *parent) + : QObject(parent) + , DObject(*new XdgActivationPrivate(this)) +{ +} + +XdgActivation::~XdgActivation() = default; + +bool XdgActivation::isActive() const +{ + auto *activation = activationV1(); + const bool active = activation && activation->isActive(); + qCDebug(trayXdgActivation) << "isActive:" << active; + return active; +} + +void XdgActivation::requestToken(QWindow *window, const QString &appId) +{ + D_D(XdgActivation); + + if (d->provider) { + qCWarning(trayXdgActivation) << "XDG activation token request already started"; + return; + } + + if (!isActive()) { + qCDebug(trayXdgActivation) << "xdg_activation_v1 is not active; token request skipped"; + Q_EMIT tokenReady({}); + return; + } + + const QString effectiveAppId = appId.isEmpty() ? QString::fromUtf8(DTK_CORE_NAMESPACE::DSGApplication::id()) : appId; + if (effectiveAppId.isEmpty()) + qCWarning(trayXdgActivation) << "XDG activation request has empty app id"; + + auto effectiveWindow = window ? window : QGuiApplication::focusWindow(); + if (!effectiveWindow) { + qCWarning(trayXdgActivation) << "XDG activation request has no target window"; + Q_EMIT tokenReady({}); + return; + } + + auto *provider = activationV1()->createTokenProvider(effectiveWindow, effectiveAppId); + provider->setParent(this); + d->provider = provider; + + connect(provider, &XdgActivationTokenV1::done, this, [this, provider, effectiveAppId](const QString &token) { + D_D(XdgActivation); + d->provider = nullptr; + + if (token.isEmpty()) + qCWarning(trayXdgActivation) << "XDG activation token missing for app:" << effectiveAppId; + else + qCDebug(trayXdgActivation) << "XDG activation token received for app:" << effectiveAppId; + + provider->deleteLater(); + Q_EMIT tokenReady(token); + }, Qt::SingleShotConnection); +} + +} // namespace tray + +#include "xdgactivation.moc" diff --git a/src/tray-wayland-integration/xdgactivation.h b/src/tray-wayland-integration/xdgactivation.h new file mode 100644 index 000000000..83b1c40c9 --- /dev/null +++ b/src/tray-wayland-integration/xdgactivation.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +namespace tray { + +class XdgActivationPrivate; +class XdgActivation : public QObject, public DTK_CORE_NAMESPACE::DObject +{ + Q_OBJECT + D_DECLARE_PRIVATE(XdgActivation) +public: + explicit XdgActivation(QObject *parent = nullptr); + ~XdgActivation() override; + + bool isActive() const; + + void requestToken(QWindow *window = nullptr, const QString &appId = {}); + +Q_SIGNALS: + void tokenReady(const QString &token); +}; + +} // namespace tray diff --git a/src/tray-wayland-integration/xdgactivation_p.h b/src/tray-wayland-integration/xdgactivation_p.h new file mode 100644 index 000000000..987ae6260 --- /dev/null +++ b/src/tray-wayland-integration/xdgactivation_p.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "xdgactivation.h" + +#include + +#include + +namespace tray { + +class XdgActivationTokenV1; + +class XdgActivationPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate +{ +public: + explicit XdgActivationPrivate(XdgActivation *qq); + ~XdgActivationPrivate() override; + + QPointer provider; + + D_DECLARE_PUBLIC(XdgActivation) +}; + +} // namespace tray