Files
xembed-fluff/doc.md
2025-12-18 21:10:53 +08:00

9.6 KiB
Raw Permalink Blame History

xembed-traymanager-proxy — 改造文档

此文档描述从 xembed-sni-proxy 改造而来的 xembed-traymanager-proxy 项目。改造的核心是将 X11 Xembed 系统托盘管理功能从 Go daemon 分离到独立的 C++/Qt 组件,并通过 DBus 与后端 TrayManager1 通信。

项目概述

  • 原名:xembed-sni-proxy
  • 现名:xembed-traymanager-proxy
  • 原作用:在 X11/XEmbed 客户端与 KDE/StatusNotifierItem (SNI) 系统之间作桥接
  • 现作用:在 X11/XEmbed 客户端与 Deepin TrayManager1 DBus 后端之间作桥接
  • 语言C++(使用 Qt 框架)
  • 构建系统CMake

架构改造对比

方面 原 xembed-sni-proxy 新 xembed-traymanager-proxy
Selection Owner 注册并持有 注册并持有(保留)
Icon 处理 SNIProxy → 注册 SNI DBus 项 TrayManagerProxy → 调用 TrayManager1 DBus
DBus 暴露 每个 icon 一个 StatusNotifierItem 对象 通过 TrayManager1 后端统一管理
DBus 服务名 org.kde.StatusNotifierWatcher org.deepin.dde.TrayManager1
消息格式 KDbusImageStruct, KDbusToolTipStruct uint32 窗口ID + 图像数据
后端支持 KDE Plasma SNI 管理器 dde-daemon trayicon1 组件

源码布局(改造后)

  • main.cpp — 启动代码,简化了 SNI 初始化
  • fdoselectionmanager.cpp / fdoselectionmanager.h — 保留 selection owner 管理逻辑,改为创建 TrayManagerProxy
  • traymanagerproxy.cpp / traymanagerproxy.h 新增,替代 sniproxy通过 DBus 与 TrayManager1 通信
  • xtestsender.cpp / xtestsender.h — 保留,用于点击事件注入
  • xcbutils.h — 保留X11/xcb 辅助函数
  • org.deepin.dde.TrayManager1.xml 新增TrayManager1 DBus 接口定义
  • org.kde.StatusNotifierItem.xml, org.kde.StatusNotifierWatcher.xml 删除
  • snidbus.cpp / snidbus.h 删除
  • sniproxy.cpp / sniproxy.h 删除(替代为 traymanagerproxy
  • statusnotifieritemadaptor.* 删除CMake 生成)
  • statusnotifierwatcher_interface.* 删除CMake 生成)
  • CMakeLists.txt — 改为生成 TrayManager1 接口代码

关键改造点详解

  1. TrayManagerProxy新增

    • 替代原有的 SNIProxy
    • 职责:
      • 接收 X11 tray icon 窗口
      • 创建容器窗口reparent icon
      • 监听 XDamage 事件,抓取 icon 窗口图像
      • 通过 DBus 调用 org.deepin.dde.TrayManager1 接口:
        • 获取窗口名
        • 注册/更新/移除 icon
    • 与原 SNIProxy 的区别:
      • SNIProxy: 自己注册 StatusNotifierItem DBus 对象
      • TrayManagerProxy: 将数据发送给后端 TrayManager1
  2. FdoSelectionManager改造

    • 保留selection owner 管理、X11 事件处理
    • 改变:创建 new TrayManagerProxy(winId) 而非 new SNIProxy(winId)
    • 新增:initTrayManager() 方法(目前为桩)
  3. org.deepin.dde.TrayManager1.xml新增

    <interface name="org.deepin.dde.TrayManager1">
      <property name="TrayIcons" type="au" access="read"/>
      <method name="Manage"/>, <method name="GetName"/>, <method name="EnableNotification"/>
      <signal name="Added"/>, <signal name="Removed"/>, <signal name="Changed"/>, <signal name="Inited"/>
    </interface>
    
  4. main.cpp简化

    • 删除:qDBusRegisterMetaType<KDbusImageStruct> 等 SNI 类型注册
    • 删除:#include "snidbus.h"
    • 保留:基础 Qt/X11 初始化

运行时交互流程

应用窗口尝试 dock
        ↓
xembed-traymanager-proxy (C++)
  ├─ 监听到 _NET_SYSTEM_TRAY_OPCODE ClientMessage
  ├─ 创建 TrayManagerProxy 对象
  ├─ TrayManagerProxy 连接到 TrayManager1 DBus
  ├─ 调用 TrayManager1::RegisterTrayIcon(windowId, iconPath, name)
  ├─ 监听 XDamage 事件
  └─ 定期更新TrayManager1::UpdateTrayIcon(windowId, ...)
        ↓
go daemon (dde-daemon/trayicon1)
  ├─ 接收 DBus 调用
  ├─ 维护内部 icon 列表
  ├─ 发出 Added/Changed/Removed 信号
  └─ 暴露 TrayIcons 属性
        ↓
上层 UI (如 shell 面板)
  ├─ 监听 TrayManager1 信号
  ├─ 读取 TrayIcons 属性
  └─ 展示图标

构建和部署

cd /path/to/xembed-sni-proxy
mkdir build && cd build
cmake ..
make
sudo make install

与 dde-daemon 的协作

  1. dde-daemon/trayicon1/daemon.go

    • enableTraySelectionManager 应保持为 false
    • 不处理 X11 tray events
    • 只维护 TrayManager1 DBus 对象和 StatusNotifierWatcher
  2. 何时启用

    • 当 xembed-traymanager-proxy 运行时,所有 X11 tray 请求由它处理
    • 当 xembed-traymanager-proxy 不运行时,可选择由 Go daemon 处理(设置 enableTraySelectionManager = true

注意事项

  • TrayManager1 接口目前是 代理接口C++ 项目作为客户端调用)
  • 完整的 TrayManager1 实现应在 dde-daemon/trayicon1 中添加对应的 DBus 方法处理
  • 当前实现假设 TrayManager1 后端已启动并可用
  1. 当用户点击图标时SNI 管理器可能向 StatusNotifierItem 发送激活请求,代理将此映射为对 XEmbed 客户端发送激活事件(例如合成鼠标事件或发送客户端消息)。
  2. 如果客户端更新图标或 tooltip代理侦测变化并通过 DBus 发出属性更改以通知 SNI 管理器更新显示。

DBus 接口与方法(重点)

  • org.kde.StatusNotifierItem(导出在每个托盘项对象上):

    • 属性:IconPixmap, IconName, Category, Id, Title, Status, WindowId 等。
    • 方法/信号:Activate, SecondaryActivate, ContextMenu, NewTitle, NewIcon 等(具体以 xml 文件 / 项目实现为准)。
  • org.kde.StatusNotifierWatcher(用于注册):

    • RegisterStatusNotifierItem / RegisterStatusNotifierWatcher — 通知 watcher 新的 item 注册或 watcher 本身注册至全局。

请参见 org.kde.StatusNotifierItem.xmlorg.kde.StatusNotifierWatcher.xml 以获得精确的方法/信号签名。

构建与依赖

  • 必要依赖:
    • Qt5/Qt6QtCore, QtDBus, QtWidgets, QtGui
    • libxcb / X11 开发头文件(用于处理 XEmbed
    • CMake构建系统
  • 构建步骤(开发机示例):
mkdir -p build && cd build
cmake ..
cmake --build .
  • 开发辅助:compile_commands.json 可用于代码导航与静态分析。

部署与打包

  • 二进制通常安装到系统的 /usr/bin 或相应位置。
  • plasma-xembedsniproxy.service.in 用于生成一个 systemd 用户服务或系统服务(视打包策略),确保在会话启动时自动运行代理。
  • xembedsniproxy.desktop 允许桌面环境识别该代理并在必要时将其作为会话组件启动。

日志、错误处理与调试

  • 使用 Qt 的日志系统(qDebug(), qWarning(), qCritical()),并在必要时提供命令行开关提高日志级别。
  • build/ 中包含生成的 moc 文件与中间产物,便于调试源码。
  • 为了重现 XEmbed 行为,可使用 xtestsender 模拟客户端事件。测试时建议在独立的 X 会话或使用 Xvfb 来避免影响主会话。

线程模型与并发注意事项

  • 代理主要运行在 Qt 的主线程事件循环。X11 与 Qt 的事件处理必须在同一线程内(通常是主线程),因此避免在后台线程直接访问 X11 资源。
  • 如果需要异步操作(如图标解码、网络等),请使用 Qt 的信号/槽机制与线程安全的队列,避免直接跨线程更新 GUI/DBus 导出对象。

测试策略

  • 单元测试:对非 UI 的纯逻辑(例如 fdoselectionmanager 中的选择逻辑)编写单元测试。
  • 集成测试:使用 xtestsender 配合临时的 SNI watcher 模拟完整注册/激活/更新流程。
  • 手动测试:在一个受控的 X 会话中运行代理并观察与系统面板的交互;使用 dbus-monitor 监听 org.kde.StatusNotifier* 相关的消息以验证代理行为。

安全与权限

  • 代理通常在用户会话中运行(无需 root 权限)。
  • 不要在代理中执行不受信任内容(例如解析远程图标数据)而不做必要的验证与资源限制。

扩展点与改进建议

  • Wayland 支持:当前项目以 XEmbed/X11 为中心,要支持 Wayland 需考虑客户端兼容策略(例如使用 XWayland 或转为基于 DBus 的原生实现)。
  • 图标缓存层:增加一个图标缓存以减小频繁解码或网络请求对性能的影响。
  • 更健壮的菜单代理:将客户端的菜单模型映射为 SNI 兼容的结构,并在需要时异步加载/延迟渲染菜单项。
  • 单元与集成测试完善:将 xtestsender 扩展为可自动化运行的测试套件并集成到 CI。

贡献指南(简要)

  • 代码风格:遵循现有 C++/Qt 风格;使用合理的命名和清晰的所有权语义(优先使用智能指针与 Qt 的 parent 机制)。
  • 提交:每个功能分支附带描述、相关测试与打包说明。
  • 运行静态检查:建议运行 clang-tidy/clang-format 并确保 compile_commands.json 可用以便工具检测。

常见文件与符号对照(快速索引)

  • main.cpp — 启动点,初始化服务
  • snidbus.* — DBus 层与 adaptor
  • sniproxy.* — 代理主逻辑
  • fdoselectionmanager.* — 名称/服务选择与策略
  • xtestsender.* — 模拟和测试
  • org.kde.StatusNotifier*.xml — DBus 接口定义

如果你希望我将文档改为英文版、生成更详细的类关系图(例如 PlantUML、或把这份文档添加到项目 README 中并更新 CMakeLists.txt 包含安装规则,我可以继续操作。