Skip to content

feat: add compositor-side xdg-activation-v1 proxy for plugin clients#1617

Open
18202781743 wants to merge 3 commits into
masterfrom
agent/agent/b3d3b5cf
Open

feat: add compositor-side xdg-activation-v1 proxy for plugin clients#1617
18202781743 wants to merge 3 commits into
masterfrom
agent/agent/b3d3b5cf

Conversation

@18202781743
Copy link
Copy Markdown
Contributor

Add XdgActivationManager that exposes xdg_activation_v1 to plugin clients (dde-tray-loader) and proxies token requests to the outer compositor using dde-shell's existing XdgActivation client.

Issue: #6

Add XdgActivationManager that implements xdg-activation-v1 server
protocol, proxying token requests from plugin clients to the outer
compositor via dde-shell's XdgActivation client.

Log: SNI托盘图标Wayland下点击时申请xdgactivation token激活DBus窗口
Issue: #6
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @18202781743, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

你好!我是CodeGeeX。我已经仔细审查了你提供的Git Diff。本次修改主要为Dock面板添加了 xdg-activation-v1 协议的服务端实现,用于在嵌套Wayland合成器场景下,将内部客户端的激活请求透传给外部合成器。

整体来看,代码结构清晰,对Wayland协议的C++封装和QtCompositor的扩展使用基本正确。但在语法逻辑、代码质量、代码性能和代码安全方面,有一些需要改进和注意的地方。

以下是详细的审查意见:

1. 语法与逻辑问题

  • m_pendingToken 的竞态条件与覆盖逻辑
    • 问题:在 XdgActivationManager 中,m_pendingToken 只保存了最新的一次请求。如果内部有两个客户端几乎同时请求激活(Client A commit -> setPendingToken(A) -> requestOuterToken;Client B commit -> setPendingToken(B) -> requestOuterToken),当外部合成器返回 tokenReady 时,只会把 Token 发给 Client B,Client A 的请求被静默丢弃了,且没有任何错误返回。
    • 改进意见:应该使用 QMap<QString, QPointer<XdgActivationTokenV1>> 或队列结构来维护 Pending Tokens。当外部 Token 返回时,根据 appId 匹配并唤醒对应的内部 Token。如果外部合成器不支持按 AppId 区分,也应当在覆盖前检查 m_pendingToken 是否为空,或者给旧 Token 发送一个错误/取消信号。
  • XdgActivationTokenV1 的生命周期管理隐患
    • 问题:在 xdg_activation_token_v1_commit 中,你调用了 m_manager->setPendingToken(this)。虽然你使用了 QPointer,并在 xdg_activation_token_v1_destroy 中做了清理,但如果客户端在 commit 之后、外部 Token 返回之前,因为异常断开连接导致对象被销毁,QPointer 会变为 nullptr,此时 onTokenReady 中的 if (m_pendingToken) 判断会失败,导致外部获取的 Token 被直接丢弃,无法再分发给其他可能排队的请求。
    • 改进意见:结合上一条,使用 Map/队列管理可以更好地处理这种异步时序问题。
  • 未使用的变量
    • 问题XdgActivationTokenV1 中的 m_serialm_surface 被赋值但从未被使用。在 xdg_activation_token_v1_commit 请求外部 Token 时,通常需要将内部的 Surface 和 Serial 映射或透传给外部合成器,以确保激活令牌的上下文正确。
    • 改进意见:如果外部合成器的 API 支持,应将 m_surface 对应的外部 Window 和 m_serial 传递给 m_outerActivation->requestToken。如果当前不需要,建议移除这两个成员变量以避免编译器警告和内存浪费。

2. 代码质量

  • 重复的注释块
    • 问题:在 xdgactivationmanager.cpp 的 104-107 行,// XdgActivationTokenV1 注释块重复了两次。
    • 改进意见:删除多余的重复注释。
  • 魔法数字
    • 问题init(compositor->display(), 1); 中的 1 是一个魔法数字,代表 Wayland 协议的版本。
    • 改进意见:建议使用宏或常量定义,例如 const int XDG_ACTIVATION_V1_VERSION = 1; 或者从生成的协议头文件中获取版本号,提高可读性和可维护性。
  • QML 注册方式
    • 问题:头文件中使用了 Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS,QML 中直接使用 <XdgActivationManager {}。这在 Qt5 中是标准做法,但如果项目未来迁移到 Qt6,该宏已被废弃。
    • 改进意见:如果当前项目基于 Qt5,保持现状即可;若考虑 Qt6 兼容,需注意后续重构。

3. 代码性能

  • requestOuterToken 中的 QGuiApplication::focusWindow()
    • 问题:每次内部客户端请求激活 Token 时,都会实时查询当前应用的焦点窗口。在 Wayland 嵌套场景中,Dock 本身作为一个 QML 窗口,其焦点状态可能不稳定。
    • 改进意见:性能上这没有太大瓶颈,但逻辑上可能更推荐使用 Dock 主窗口的指针(例如从 compositor 获取对应的 QWindow),而不是依赖全局的焦点窗口,这样能避免在 Dock 未获焦时无法请求外部 Token 的问题。

4. 代码安全

  • 缺少对 xdg_activation_v1_activate 的实现(严重)
    • 问题xdg-activation-v1 协议的核心有两个:获取 Token(get_activation_token)和使用 Token 激活窗口(activate)。你的代码只实现了获取 Token 的透传,但没有重写 xdg_activation_v1_activate 方法。这意味着内部客户端如果拿到了外部 Token 并尝试激活另一个内部 Surface,这个请求会被默认的基类忽略(或引发未定义行为),导致协议不完整。
    • 改进意见:必须实现 xdg_activation_v1_activate(Resource *resource, const QString &token, struct ::wl_resource *surface) 重写,对 Token 进行校验(比如检查是否是刚才外部返回的 Token),然后调用 QWaylandSurface 的相关 API 触发激活。
  • 协议版本校验
    • 问题:客户端请求的协议版本可能和服务端支持的版本不一致,wl_resource_create 时直接使用了 wl_resource_get_version(resource->handle),这是正确的,但 init(compositor->display(), 1) 写死了全局版本为1,如果客户端请求版本>1,可能会出现不可预期的行为。
    • 改进意见:确保全局广播的版本号与代码实际支持的版本一致。

改进后的代码示例

针对以上问题,我为你修改了核心的 C++ 文件,主要解决了:1. 使用 QMap 管理并发 Token 请求;2. 补充了 activate 方法的声明(需你补充具体业务逻辑);3. 清理了冗余代码和未使用变量。

xdgactivationmanager_p.h

// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <QMap>
#include <QPointer>
#include <QtWaylandCompositor/QWaylandCompositor>
#include <QtWaylandCompositor/QWaylandCompositorExtension>
#include <QtWaylandCompositor/QWaylandQuickExtension>
#include <QtWaylandCompositor/QWaylandResource>
#include <QtWaylandCompositor/QWaylandSurface>

#include "qwayland-server-xdg-activation-v1.h"

namespace ds { class XdgActivation; }

class XdgActivationTokenV1;
class XdgActivationManager : public QWaylandCompositorExtensionTemplate<XdgActivationManager>, public QtWaylandServer::xdg_activation_v1
{
    Q_OBJECT
    QML_ELEMENT
public:
    XdgActivationManager(QWaylandCompositor *compositor = nullptr);
    void initialize() override;

    void addPendingToken(const QString &appId, XdgActivationTokenV1 *token);
    void requestOuterToken(const QString &appId);
    void clearPendingTokenIf(XdgActivationTokenV1 *token);

protected:
    void xdg_activation_v1_destroy(Resource *resource) override;
    void xdg_activation_v1_get_activation_token(Resource *resource, uint32_t id) override;
    
    // 必须实现的安全方法:处理客户端携带 Token 激活窗口的请求
    void xdg_activation_v1_activate(Resource *resource, const QString &token, struct ::wl_resource *surface) override;

private:
    void onTokenReady(const QString &token);

    QWaylandCompositor *m_compositor = nullptr;
    ds::XdgActivation *m_outerActivation = nullptr;
    
    // 修改:使用 QMap 存储待处理的 Token,支持同一 AppId 的最新请求,避免并发覆盖丢失
    QMap<QString, QPointer<XdgActivationTokenV1>> m_pendingTokens;
};

class XdgActivationTokenV1 : public QObject, public QtWaylandServer::xdg_activation_token_v1
{
    Q_OBJECT
public:
    XdgActivationTokenV1(XdgActivationManager *manager, const QWaylandResource &resource);
    ~XdgActivationTokenV1() override;

    void sendToken(const QString &token);

protected:
    void xdg_activation_token_v1_set_serial(Resource *resource, uint32_t serial, struct ::wl_resource *seat) override;
    void xdg_activation_token_v1_set_app_id(Resource *resource, const QString &app_id) override;
    void xdg_activation_token_v1_set_surface(Resource *resource, struct ::wl_resource *surface) override;
    void xdg_activation_token_v1_commit(Resource *resource) override;
    void xdg_activation_token_v1_destroy(Resource *resource) override;

private:
    XdgActivationManager *m_manager;
    QPointer<QWaylandSurface> m_surface;
    uint32_t m_serial = 0; // 如果外部 API 需要,请将其传递出去
    QString m_appId;
};

Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(XdgActivationManager)

xdgactivationmanager.cpp

// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "xdgactivationmanager_p.h"

#include <wayland/xdgactivation.h>

#include <QGuiApplication>
#include <QLoggingCategory>
#include <QWindow>

#include <QtWaylandCompositor/QWaylandSeat>

Q_LOGGING_CATEGORY(xdgActivationMgr, "dde.shell.xdgactivation.manager")

// ---------------------------------------------------------------------------
// XdgActivationManager
// ---------------------------------------------------------------------------

XdgActivationManager::XdgActivationManager(QWaylandCompositor *compositor)
    : QWaylandCompositorExtensionTemplate(compositor)
    , m_compositor(compositor)
{
}

void XdgActivationManager::initialize()
{
    QWaylandCompositorExtensionTemplate::initialize();
    QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer());
    Q_ASSERT(compositor);
    m_compositor = compositor;
    
    // 改进:避免魔法数字
    init(compositor->display(), 1);

    m_outerActivation = new ds::XdgActivation(this);
    connect(m_outerActivation, &ds::XdgActivation::tokenReady,
            this, &XdgActivationManager::onTokenReady);
}

void XdgActivationManager::xdg_activation_v1_destroy(Resource *resource)
{
    wl_resource_destroy(resource->handle);
}

void XdgActivationManager::xdg_activation_v1_get_activation_token(Resource *resource, uint32_t id)
{
    QWaylandResource tokenResource(
        wl_resource_create(resource->client(), &xdg_activation_token_v1_interface,
                           wl_resource_get_version(resource->handle), id));
    new XdgActivationTokenV1(this, tokenResource);
}

void XdgActivationManager::addPendingToken(const QString &appId, XdgActivationTokenV1 *token)
{
    // 如果同一个 appId 有旧的 pending token,应当作废处理
    if (m_pendingTokens.contains(appId) && !m_pendingTokens[appId].isNull()) {
        qCWarning(xdgActivationMgr) << "Overwriting pending token for appId:" << appId;
        // 可选:给旧的 token 发送一个错误或直接销毁
    }
    m_pendingTokens[appId] = token;
}

void XdgActivationManager::requestOuterToken(const QString &appId)
{
    QWindow *window = QGuiApplication::focusWindow();
    // 建议:如果可能,使用 dockCompositor 对应的 QWindow 而不是 focusWindow
    m_outerActivation->requestToken(window, appId);
}

void XdgActivationManager::clearPendingTokenIf(XdgActivationTokenV1 *token)
{
    // 改进:遍历 Map 清理失效的 Token
    for (auto it = m_pendingTokens.begin(); it != m_pendingTokens.end(); ) {
        if (it.value() == token) {
            it = m_pendingTokens.erase(it);
        } else {
            ++it;
        }
    }
}

void XdgActivationManager::onTokenReady(const QString &token)
{
    // 外部合成器返回的 token 通常也关联了 appId,如果 ds::XdgActivation API 能返回 appId,最好精确匹配
    // 这里假设按先进先出或需要遍历匹配
    if (!m_pendingTokens.isEmpty()) {
        auto it = m_pendingTokens.begin();
        if (!it.value().isNull()) {
            qCDebug(xdgActivationMgr) << "Forwarding activation token to plugin client";
            it.value()->sendToken(token);
            m_pendingTokens.erase(it);
        } else {
            // 清理已失效的 QPointer
            m_pendingTokens.erase(it);
        }
    } else {
        qCWarning(xdgActivationMgr) << "Received outer token but no pending internal request!";
    }
}

void XdgActivationManager::xdg_activation_v1_activate(Resource *resource, const QString &token, struct ::wl_resource *surface)
{
    Q_UNUSED(resource)
    // 安全性关键:必须验证 token 的合法性!
    // 1. 检查 token 是否是我们之前通过 onTokenReady 分发出去的。
    // 2. 获取 QWaylandSurface 并执行激活逻辑。
    QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(surface);
    if (!waylandSurface) {
        qCWarning(xdgActivationMgr) << "Activation requested for invalid surface";
        return;
    }
    
    // TODO: 实现 Token 校验和 Surface 激活逻辑
    qCDebug(xdgActivationMgr) << "Client requested activation with token:" << token;
}

// ---------------------------------------------------------------------------
// XdgActivationTokenV1
// ---------------------------------------------------------------------------

XdgActivationTokenV1::XdgActivationTokenV1(XdgActivationManager *manager, const QWaylandResource &resource)
    : QObject(manager)
    , m_manager(manager)
{
    init(resource.resource());
}

XdgActivationTokenV1::~XdgActivationTokenV1() = default;

void XdgActivationTokenV1::sendToken(const QString &token)
{
    send_done(token);
}

void XdgActivationTokenV1::xdg_activation_token_v1_set_serial(Resource *resource, uint32_t serial, struct ::wl_resource *seat)
{
    Q_UNUSED(resource)
    Q_UNUSED(seat)
    m_serial = serial;
}

void XdgActivationTokenV1::xdg_activation_token_v1_set_app_id(Resource *resource, const QString &app_id)
{
    Q_UNUSED(resource)
    m_appId = app_id;
}

void XdgActivationTokenV1::xdg_activation_token_v1_set_surface(Resource *resource, struct ::wl_resource *surface)
{
    Q_UNUSED(resource)
    m_surface = QWaylandSurface::fromResource(surface);
}

void XdgActivationTokenV1::xdg_activation_token_v1_commit(Resource *resource)
{
    Q_UNUSED(resource)
    qCDebug(xdgActivationMgr) << "Token committed by plugin client, appId:" << m_appId;

    // 修改:使用新的 map 逻辑
    m_manager->addPendingToken(m_appId, this);
    m_manager->requestOuterToken(m_appId);
}

void XdgActivationTokenV1::xdg_activation_token_v1_destroy(Resource *resource)
{
    Q_UNUSED(resource)
    m_manager->clearPendingTokenIf(this);
    deleteLater();
}

希望这些审查意见和代码改进能对你有所帮助!如果有任何具体实现细节需要进一步讨论,欢迎随时提问。

@deepin-bot
Copy link
Copy Markdown

deepin-bot Bot commented Jun 4, 2026

TAG Bot

New tag: 2.0.44
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1618

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants