Skip to content

Commit 5eb8362

Browse files
committed
feat: add xdg-activation-v1 protocol support for window activation
tokens Implement integration with the xdg-activation-v1 Wayland protocol to support requesting activation tokens, both synchronously (with a 2-second timeout) and asynchronously. This enables proper window activation and focus-stealing prevention on Wayland compositors. Key components: - XdgActivation class: singleton providing requestToken() and requestTokenAsync() methods - XdgActivationPrivate: extends QWaylandClientExtensionTemplate to bind the protocol - XdgActivationTokenV1: wrapper for xdg_activation_token_v1 with done signal - Uses last input device serial and seat for proper activation context - Checks for Wayland platform before attempting activation - Integrates into dde-shell-frame build system with protocol XML generation Log: Added xdg-activation-v1 protocol implementation for window activation tokens Influence: 1. Test requestToken() on X11 platforms (should return empty without error) 2. Test requestToken() on Wayland with valid window handle 3. Test requestToken() timeout behavior with unconsumed token 4. Test requestTokenAsync() callback execution 5. Verify token retrieval under compositors supporting xdg-activation-v1 6. Verify no crashes or hangs when protocol is not available feat: 添加 xdg-activation-v1 协议支持,用于窗口激活令牌 实现 xdg-activation-v1 Wayland 协议集成,支持同步(带2秒超时)和异步方 式请求激活令牌。这使得在 Wayland 合成器上能够正确进行窗口激活和防止焦点 窃取。 关键组件: - XdgActivation 类:单例,提供 requestToken() 和 requestTokenAsync() 方法 - XdgActivationPrivate:继承 QWaylandClientExtensionTemplate 以绑定协议 - XdgActivationTokenV1:xdg_activation_token_v1 的包装类,包含 done 信号 - 使用最后输入设备序列和 seat 提供正确的激活上下文 - 在尝试激活前检查是否为 Wayland 平台 - 集成到 dde-shell-frame 构建系统并生成协议 XML Log: 新增 xdg-activation-v1 协议实现,用于窗口激活令牌 Influence: 1. 在 X11 平台上测试 requestToken()(应返回空值且无错误) 2. 在 Wayland 上使用有效窗口句柄测试 requestToken() 3. 测试 requestToken() 在令牌未被消耗时的超时行为 4. 测试 requestTokenAsync() 回调函数正常执行 5. 验证在支持 xdg-activation-v1 的合成器上能够获取令牌 6. 验证协议不可用时无崩溃或挂起 PMS: BUG-295555
1 parent cb92112 commit 5eb8362

4 files changed

Lines changed: 243 additions & 0 deletions

File tree

frame/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ set(PUBLIC_HEADERS
1515
appletbridge.h
1616
qmlengine.h
1717
layershell/dlayershellwindow.h
18+
wayland/xdgactivation.h
1819
models/listtotableproxymodel.h
1920
dsutility.h
2021
)
@@ -29,6 +30,7 @@ set(PRIVATE_HEADERS
2930
private/dsqmlglobal_p.h
3031
layershell/qwaylandlayershellsurface_p.h
3132
layershell/qwaylandlayershellintegration_p.h
33+
wayland/xdgactivation_p.h
3234
models/kextracolumnsproxymodel.h
3335
quick/dsquickdrag_p.h
3436
)
@@ -58,6 +60,7 @@ add_library(dde-shell-frame SHARED
5860
panel.cpp
5961
appletproxy.cpp
6062
appletbridge.cpp
63+
wayland/xdgactivation.cpp
6164
appletitem.cpp
6265
containmentitem.cpp
6366
qmlengine.cpp
@@ -83,6 +86,7 @@ set_target_properties(dde-shell-frame PROPERTIES
8386
qt_generate_wayland_protocol_client_sources(dde-shell-frame FILES
8487
${CMAKE_CURRENT_SOURCE_DIR}/layershell/protocol/wlr-layer-shell-unstable-v1.xml
8588
${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml
89+
${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml
8690
)
8791

8892
set_target_properties(dde-shell-frame PROPERTIES

frame/wayland/xdgactivation.cpp

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

frame/wayland/xdgactivation.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 "dsglobal.h"
8+
9+
#include <DObject>
10+
#include <QObject>
11+
#include <QWindow>
12+
13+
DS_BEGIN_NAMESPACE
14+
15+
class XdgActivationPrivate;
16+
class DS_SHARE XdgActivation : public QObject, public DTK_CORE_NAMESPACE::DObject
17+
{
18+
Q_OBJECT
19+
D_DECLARE_PRIVATE(XdgActivation)
20+
public:
21+
explicit XdgActivation(QObject *parent = nullptr);
22+
~XdgActivation() override;
23+
24+
bool isActive() const;
25+
26+
void requestToken(QWindow *window = nullptr, const QString &appId = {});
27+
28+
Q_SIGNALS:
29+
void tokenReady(const QString &token);
30+
};
31+
32+
DS_END_NAMESPACE

frame/wayland/xdgactivation_p.h

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+
DS_BEGIN_NAMESPACE
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+
DS_END_NAMESPACE

0 commit comments

Comments
 (0)