Skip to content

Commit fee4de7

Browse files
18202781743claude
andcommitted
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 <noreply@anthropic.com>
1 parent 2496163 commit fee4de7

7 files changed

Lines changed: 281 additions & 4 deletions

File tree

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
1+
# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
22
#
33
# SPDX-License-Identifier: CC0-1.0
44

@@ -40,6 +40,9 @@ endif()
4040
include(GNUInstallDirs)
4141
include(CMakePackageConfigHelpers)
4242

43+
find_package(PkgConfig REQUIRED)
44+
pkg_get_variable(WAYLAND_PROTOCOLS_DATADIR wayland-protocols pkgdatadir)
45+
4346
add_subdirectory(plugins)
4447
add_subdirectory(src)
4548

plugins/application-tray/api/dbus/org.kde.StatusNotifierItem.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
<arg direction="in" type="i" name="x"/>
4242
<arg direction="in" type="i" name="y"/>
4343
</method>
44+
<method name="ProvideXdgActivationToken">
45+
<arg direction="in" type="s" name="token"/>
46+
</method>
4447
<method name="SecondaryActivate">
4548
<arg direction="in" type="i" name="x"/>
4649
<arg direction="in" type="i" name="y"/>

plugins/application-tray/sniprotocolhandler.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "util.h"
1010
#include "plugin.h"
11+
#include "xdgactivation.h"
1112

1213
#include "dbusmenuimporter.h"
1314

@@ -317,7 +318,27 @@ bool SniTrayProtocolHandler::eventFilter(QObject *watched, QEvent *event)
317318
if (event->type() == QEvent::MouseButtonRelease) {
318319
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
319320
if (mouseEvent->button() == Qt::LeftButton) {
320-
m_sniInter->Activate(0, 0);
321+
auto *activation = new XdgActivation(this);
322+
if (activation->isActive()) {
323+
auto *win = window()->windowHandle();
324+
if (!win) {
325+
activation->deleteLater();
326+
return false;
327+
}
328+
329+
auto sniInter = m_sniInter;
330+
connect(activation, &XdgActivation::tokenReady, this, [sniInter, activation](const QString &token) {
331+
if (!token.isEmpty()) {
332+
sniInter->ProvideXdgActivationToken(token);
333+
}
334+
sniInter->Activate(0, 0);
335+
activation->deleteLater();
336+
}, Qt::SingleShotConnection);
337+
activation->requestToken(win);
338+
} else {
339+
m_sniInter->Activate(0, 0);
340+
activation->deleteLater();
341+
}
321342
} else if (mouseEvent->button() == Qt::RightButton) {
322343
if (!menuImporter()) {
323344
m_sniInter->ContextMenu(0, 0);

src/tray-wayland-integration/CMakeLists.txt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
1+
# SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
22
#
33
# SPDX-License-Identifier: CC0-1.0
44

55
find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui WaylandClient)
66
if(Qt${QT_VERSION_MAJOR}_VERSION VERSION_GREATER_EQUAL 6.10)
77
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS GuiPrivate WaylandClientPrivate REQUIRED)
88
endif()
9-
find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widget)
9+
find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widget)
1010

1111
find_package(ECM REQUIRED MO_MODULE)
1212
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${ECM_MODULE_PATH}")
@@ -17,6 +17,15 @@ include(KDEInstallDirs)
1717
add_library(dockpluginmanager-interface SHARED
1818
plugin.h
1919
plugin.cpp
20+
xdgactivation.h
21+
xdgactivation_p.h
22+
xdgactivation.cpp
23+
)
24+
25+
qt_generate_wayland_protocol_client_sources(dockpluginmanager-interface
26+
NO_INCLUDE_CORE_ONLY
27+
FILES
28+
${WAYLAND_PROTOCOLS_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml
2029
)
2130

2231
target_include_directories(dockpluginmanager-interface PUBLIC
@@ -28,6 +37,14 @@ PUBLIC
2837
Qt${QT_VERSION_MAJOR}::Gui
2938
Qt${QT_VERSION_MAJOR}::Widgets
3039
Dtk${DTK_VERSION_MAJOR}::Widget
40+
PRIVATE
41+
Qt${QT_VERSION_MAJOR}::Core
42+
Qt${QT_VERSION_MAJOR}::GuiPrivate
43+
Qt${QT_VERSION_MAJOR}::WaylandClient
44+
Qt${QT_VERSION_MAJOR}::WaylandClientPrivate
45+
Dtk${DTK_VERSION_MAJOR}::Core
46+
Dtk${DTK_VERSION_MAJOR}::Gui
47+
Wayland::Client
3148
)
3249

3350
add_library(dockpluginmanager SHARED
@@ -53,6 +70,8 @@ PRIVATE
5370
Qt${QT_VERSION_MAJOR}::GuiPrivate
5471
Qt${QT_VERSION_MAJOR}::WaylandClient
5572
Qt${QT_VERSION_MAJOR}::WaylandClientPrivate
73+
Dtk${DTK_VERSION_MAJOR}::Core
74+
Dtk${DTK_VERSION_MAJOR}::Gui
5675
Wayland::Client
5776
)
5877

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
#include "xdgactivation_p.h"
6+
7+
#include <DSGApplication>
8+
#include <QGuiApplication>
9+
#include <QLoggingCategory>
10+
#include <QMetaObject>
11+
#include <QPointer>
12+
#include <QtWaylandClient/QWaylandClientExtension>
13+
14+
#include "qwayland-xdg-activation-v1.h"
15+
#include <private/qwaylanddisplay_p.h>
16+
#include <private/qwaylandinputdevice_p.h>
17+
#include <private/qwaylandwindow_p.h>
18+
19+
Q_LOGGING_CATEGORY(trayXdgActivation, "dde.tray.xdgactivation")
20+
21+
namespace tray {
22+
23+
class XdgActivationTokenV1 : public QObject, public QtWayland::xdg_activation_token_v1
24+
{
25+
Q_OBJECT
26+
27+
public:
28+
~XdgActivationTokenV1() override
29+
{
30+
destroy();
31+
}
32+
33+
Q_SIGNALS:
34+
void done(const QString &token);
35+
36+
protected:
37+
void xdg_activation_token_v1_done(const QString &token) override
38+
{
39+
Q_EMIT done(token);
40+
}
41+
};
42+
43+
namespace {
44+
45+
class XdgActivationV1 : public QWaylandClientExtensionTemplate<XdgActivationV1>,
46+
public QtWayland::xdg_activation_v1
47+
{
48+
public:
49+
XdgActivationV1()
50+
: QWaylandClientExtensionTemplate<XdgActivationV1>(1)
51+
{
52+
initialize();
53+
}
54+
55+
~XdgActivationV1() override
56+
{
57+
if (isInitialized())
58+
destroy();
59+
}
60+
61+
XdgActivationTokenV1 *createTokenProvider(QWindow *window, const QString &appId)
62+
{
63+
auto *provider = new XdgActivationTokenV1;
64+
provider->init(get_activation_token());
65+
66+
if (window) {
67+
if (auto *waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle())) {
68+
if (auto *surface = waylandWindow->wlSurface())
69+
provider->set_surface(surface);
70+
if (auto *inputDevice = waylandWindow->display()->lastInputDevice())
71+
provider->set_serial(inputDevice->serial(), inputDevice->wl_seat());
72+
}
73+
}
74+
75+
if (!appId.isEmpty())
76+
provider->set_app_id(appId);
77+
78+
provider->commit();
79+
return provider;
80+
}
81+
};
82+
83+
XdgActivationV1 *activationV1()
84+
{
85+
static QPointer<XdgActivationV1> activation;
86+
if (activation)
87+
return activation;
88+
89+
activation = new XdgActivationV1;
90+
activation->setParent(qApp);
91+
return activation;
92+
}
93+
94+
} // namespace
95+
96+
// ---------------------------------------------------------------------------
97+
// XdgActivationPrivate
98+
// ---------------------------------------------------------------------------
99+
100+
XdgActivationPrivate::XdgActivationPrivate(XdgActivation *qq)
101+
: DObjectPrivate(qq)
102+
{
103+
}
104+
105+
XdgActivationPrivate::~XdgActivationPrivate() = default;
106+
107+
// ---------------------------------------------------------------------------
108+
// XdgActivation
109+
// ---------------------------------------------------------------------------
110+
111+
XdgActivation::XdgActivation(QObject *parent)
112+
: QObject(parent)
113+
, DObject(*new XdgActivationPrivate(this))
114+
{
115+
}
116+
117+
XdgActivation::~XdgActivation() = default;
118+
119+
bool XdgActivation::isActive() const
120+
{
121+
auto *activation = activationV1();
122+
const bool active = activation && activation->isActive();
123+
qCDebug(trayXdgActivation) << "isActive:" << active;
124+
return active;
125+
}
126+
127+
void XdgActivation::requestToken(QWindow *window, const QString &appId)
128+
{
129+
D_D(XdgActivation);
130+
131+
if (d->provider) {
132+
qCWarning(trayXdgActivation) << "XDG activation token request already started";
133+
return;
134+
}
135+
136+
if (!isActive()) {
137+
qCDebug(trayXdgActivation) << "xdg_activation_v1 is not active; token request skipped";
138+
Q_EMIT tokenReady({});
139+
return;
140+
}
141+
142+
const QString effectiveAppId = appId.isEmpty() ? QString::fromUtf8(DTK_CORE_NAMESPACE::DSGApplication::id()) : appId;
143+
if (effectiveAppId.isEmpty())
144+
qCWarning(trayXdgActivation) << "XDG activation request has empty app id";
145+
146+
auto effectiveWindow = window ? window : QGuiApplication::focusWindow();
147+
if (!effectiveWindow) {
148+
qCWarning(trayXdgActivation) << "XDG activation request has no target window";
149+
Q_EMIT tokenReady({});
150+
return;
151+
}
152+
153+
auto *provider = activationV1()->createTokenProvider(effectiveWindow, effectiveAppId);
154+
provider->setParent(this);
155+
d->provider = provider;
156+
157+
connect(provider, &XdgActivationTokenV1::done, this, [this, provider, effectiveAppId](const QString &token) {
158+
D_D(XdgActivation);
159+
d->provider = nullptr;
160+
161+
if (token.isEmpty())
162+
qCWarning(trayXdgActivation) << "XDG activation token missing for app:" << effectiveAppId;
163+
else
164+
qCDebug(trayXdgActivation) << "XDG activation token received for app:" << effectiveAppId;
165+
166+
provider->deleteLater();
167+
Q_EMIT tokenReady(token);
168+
}, Qt::SingleShotConnection);
169+
}
170+
171+
} // namespace tray
172+
173+
#include "xdgactivation.moc"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
#pragma once
6+
7+
#include <DObject>
8+
#include <QObject>
9+
#include <QWindow>
10+
11+
namespace tray {
12+
13+
class XdgActivationPrivate;
14+
class XdgActivation : public QObject, public DTK_CORE_NAMESPACE::DObject
15+
{
16+
Q_OBJECT
17+
D_DECLARE_PRIVATE(XdgActivation)
18+
public:
19+
explicit XdgActivation(QObject *parent = nullptr);
20+
~XdgActivation() override;
21+
22+
bool isActive() const;
23+
24+
void requestToken(QWindow *window = nullptr, const QString &appId = {});
25+
26+
Q_SIGNALS:
27+
void tokenReady(const QString &token);
28+
};
29+
30+
} // namespace tray
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
#pragma once
6+
7+
#include "xdgactivation.h"
8+
9+
#include <dobject_p.h>
10+
11+
#include <QPointer>
12+
13+
namespace tray {
14+
15+
class XdgActivationTokenV1;
16+
17+
class XdgActivationPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate
18+
{
19+
public:
20+
explicit XdgActivationPrivate(XdgActivation *qq);
21+
~XdgActivationPrivate() override;
22+
23+
QPointer<XdgActivationTokenV1> provider;
24+
25+
D_DECLARE_PUBLIC(XdgActivation)
26+
};
27+
28+
} // namespace tray

0 commit comments

Comments
 (0)