From 9a9bb64066bf6278b4030054ca767615a76af000 Mon Sep 17 00:00:00 2001 From: yeshanshan Date: Thu, 28 May 2026 13:50:54 +0800 Subject: [PATCH] feat: add xdg-activation-v1 protocol support for window activation tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frame/CMakeLists.txt | 4 + frame/wayland/xdgactivation.cpp | 179 ++++++++++++++++++++++++++++++++ frame/wayland/xdgactivation.h | 32 ++++++ frame/wayland/xdgactivation_p.h | 28 +++++ 4 files changed, 243 insertions(+) create mode 100644 frame/wayland/xdgactivation.cpp create mode 100644 frame/wayland/xdgactivation.h create mode 100644 frame/wayland/xdgactivation_p.h diff --git a/frame/CMakeLists.txt b/frame/CMakeLists.txt index 1bfc03365..b54909545 100644 --- a/frame/CMakeLists.txt +++ b/frame/CMakeLists.txt @@ -15,6 +15,7 @@ set(PUBLIC_HEADERS appletbridge.h qmlengine.h layershell/dlayershellwindow.h + wayland/xdgactivation.h models/listtotableproxymodel.h dsutility.h ) @@ -29,6 +30,7 @@ set(PRIVATE_HEADERS private/dsqmlglobal_p.h layershell/qwaylandlayershellsurface_p.h layershell/qwaylandlayershellintegration_p.h + wayland/xdgactivation_p.h models/kextracolumnsproxymodel.h quick/dsquickdrag_p.h ) @@ -58,6 +60,7 @@ add_library(dde-shell-frame SHARED panel.cpp appletproxy.cpp appletbridge.cpp + wayland/xdgactivation.cpp appletitem.cpp containmentitem.cpp qmlengine.cpp @@ -83,6 +86,7 @@ set_target_properties(dde-shell-frame PROPERTIES qt_generate_wayland_protocol_client_sources(dde-shell-frame FILES ${CMAKE_CURRENT_SOURCE_DIR}/layershell/protocol/wlr-layer-shell-unstable-v1.xml ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml + ${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml ) set_target_properties(dde-shell-frame PROPERTIES diff --git a/frame/wayland/xdgactivation.cpp b/frame/wayland/xdgactivation.cpp new file mode 100644 index 000000000..ca5675cf2 --- /dev/null +++ b/frame/wayland/xdgactivation.cpp @@ -0,0 +1,179 @@ +// 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 + +#include "qwayland-xdg-activation-v1.h" +#include +#include +#include + +Q_LOGGING_CATEGORY(dsXdgActivation, "org.deepin.ds.xdgactivation") + +DS_BEGIN_NAMESPACE + +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 +{ + if (!Dtk::Gui::DGuiApplicationHelper::testAttribute(Dtk::Gui::DGuiApplicationHelper::IsWaylandPlatform)) { + qCDebug(dsXdgActivation) << "not running on Wayland, isActive returns false"; + return false; + } + + auto *activation = activationV1(); + const bool active = activation && activation->isActive(); + qCDebug(dsXdgActivation) << "isActive:" << active; + return active; +} + +void XdgActivation::requestToken(QWindow *window, const QString &appId) +{ + D_D(XdgActivation); + + if (d->provider) { + qCWarning(dsXdgActivation) << "XDG activation token request already started"; + return; + } + + if (!isActive()) { + qCDebug(dsXdgActivation) << "xdg_activation_v1 is not active; token request skipped"; + Q_EMIT tokenReady({}); + return; + } + + const QString effectiveAppId = appId.isEmpty() ? QString::fromUtf8(Dtk::Core::DSGApplication::id()) : appId; + if (effectiveAppId.isEmpty()) + qCWarning(dsXdgActivation) << "XDG activation request has empty app id"; + + auto effectiveWindow = window ? window : QGuiApplication::focusWindow(); + if (!effectiveWindow) { + qCWarning(dsXdgActivation) << "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(dsXdgActivation) << "XDG activation token missing for app:" << effectiveAppId; + else + qCDebug(dsXdgActivation) << "XDG activation token received for app:" << effectiveAppId; + + provider->deleteLater(); + Q_EMIT tokenReady(token); + }, Qt::SingleShotConnection); +} + +DS_END_NAMESPACE + +#include "xdgactivation.moc" diff --git a/frame/wayland/xdgactivation.h b/frame/wayland/xdgactivation.h new file mode 100644 index 000000000..7f0ecf918 --- /dev/null +++ b/frame/wayland/xdgactivation.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dsglobal.h" + +#include +#include +#include + +DS_BEGIN_NAMESPACE + +class XdgActivationPrivate; +class DS_SHARE 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); +}; + +DS_END_NAMESPACE diff --git a/frame/wayland/xdgactivation_p.h b/frame/wayland/xdgactivation_p.h new file mode 100644 index 000000000..c7a7dad59 --- /dev/null +++ b/frame/wayland/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 + +DS_BEGIN_NAMESPACE + +class XdgActivationTokenV1; + +class XdgActivationPrivate : public DTK_CORE_NAMESPACE::DObjectPrivate +{ +public: + explicit XdgActivationPrivate(XdgActivation *qq); + ~XdgActivationPrivate() override; + + QPointer provider; + + D_DECLARE_PUBLIC(XdgActivation) +}; + +DS_END_NAMESPACE