diff --git a/CMakeLists.txt b/CMakeLists.txt index 135a220..de7acd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,6 @@ set(XEMBED_SNI_PROXY_SOURCES main.cpp fdoselectionmanager.cpp fdoselectionmanager.h traymanager1.cpp traymanager1.h - traymanagerproxy.cpp traymanagerproxy.h xtestsender.cpp xtestsender.h ) diff --git a/doc.md b/doc.md new file mode 100644 index 0000000..3dbf181 --- /dev/null +++ b/doc.md @@ -0,0 +1,207 @@ +# 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(新增)** + ```xml + + + , , + , , , + + ``` + +4. **main.cpp(简化)** + - 删除:`qDBusRegisterMetaType` 等 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 属性 + └─ 展示图标 +``` + +**构建和部署** + +```bash +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 后端已启动并可用 +4. 当用户点击图标时,SNI 管理器可能向 `StatusNotifierItem` 发送激活请求,代理将此映射为对 XEmbed 客户端发送激活事件(例如合成鼠标事件或发送客户端消息)。 +5. 如果客户端更新图标或 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.xml` 与 `org.kde.StatusNotifierWatcher.xml` 以获得精确的方法/信号签名。 + +**构建与依赖** + +- 必要依赖: + - Qt5/Qt6(QtCore, QtDBus, QtWidgets, QtGui) + - libxcb / X11 开发头文件(用于处理 XEmbed) + - CMake(构建系统) +- 构建步骤(开发机示例): + +```bash +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` 包含安装规则,我可以继续操作。 \ No newline at end of file diff --git a/fdoselectionmanager.cpp b/fdoselectionmanager.cpp index 8a75672..d0e6dfb 100644 --- a/fdoselectionmanager.cpp +++ b/fdoselectionmanager.cpp @@ -14,13 +14,13 @@ #include +#include #include #include #include #include #include "traymanager1.h" -#include "traymanagerproxy.h" #include "xcbutils.h" #define SYSTEM_TRAY_REQUEST_DOCK 0 @@ -128,25 +128,25 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m // } } else if (responseType == XCB_DESTROY_NOTIFY) { const auto destroyedWId = reinterpret_cast(ev)->window; - if (m_proxies.contains(destroyedWId)) { + if (m_trayManager->haveIcon(destroyedWId)) { undock(destroyedWId); } } else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) { - const auto damagedWId = reinterpret_cast(ev)->drawable; - const auto tmProxy = m_proxies.value(damagedWId); - if (tmProxy) { - tmProxy->update(); - xcb_damage_subtract(m_x11Interface->connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE); - } + // const auto damagedWId = reinterpret_cast(ev)->drawable; + // const auto tmProxy = m_proxies.value(damagedWId); + // if (tmProxy) { + // tmProxy->update(); + // xcb_damage_subtract(m_x11Interface->connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE); + // } } else if (responseType == XCB_CONFIGURE_REQUEST) { - const auto event = reinterpret_cast(ev); - const auto tmProxy = m_proxies.value(event->window); - if (tmProxy) { - // The embedded window tries to move or resize. Ignore move, handle resize only. - if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { - tmProxy->resizeWindow(event->width, event->height); - } - } + // const auto event = reinterpret_cast(ev); + // const auto tmProxy = m_proxies.value(event->window); + // if (tmProxy) { + // // The embedded window tries to move or resize. Ignore move, handle resize only. + // if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { + // tmProxy->resizeWindow(event->width, event->height); + // } + // } } return false; @@ -154,38 +154,36 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m void FdoSelectionManager::dock(xcb_window_t winId) { + Q_CHECK_PTR(m_trayManager); qCDebug(SNIPROXY) << "trying to dock window " << winId; - if (m_proxies.contains(winId)) { + if (m_trayManager->haveIcon(winId)) { return; } if (addDamageWatch(winId)) { - auto proxy = new TrayManagerProxy(winId, this); - m_proxies[winId] = proxy; + // auto proxy = new TrayManagerProxy(winId, this); + // m_proxies[winId] = proxy; // Register with TrayManager1 if available - if (m_trayManager) { - m_trayManager->registerIcon(winId, proxy); - } + m_trayManager->registerIcon(winId); } } void FdoSelectionManager::undock(xcb_window_t winId) { + Q_CHECK_PTR(m_trayManager); qCDebug(SNIPROXY) << "trying to undock window " << winId; - if (!m_proxies.contains(winId)) { + if (m_trayManager->haveIcon(winId)) { return; } // Unregister from TrayManager1 if available - if (m_trayManager) { - m_trayManager->unregisterIcon(winId); - } + m_trayManager->unregisterIcon(winId); - m_proxies[winId]->deleteLater(); - m_proxies.remove(winId); + // m_proxies[winId]->deleteLater(); + // m_proxies.remove(winId); } void FdoSelectionManager::onClaimedOwnership() diff --git a/fdoselectionmanager.h b/fdoselectionmanager.h index 0af1f59..672ae79 100644 --- a/fdoselectionmanager.h +++ b/fdoselectionmanager.h @@ -48,6 +48,6 @@ private: uint8_t m_damageEventBase; QHash m_damageWatches; - QHash m_proxies; + // QHash m_proxies; KSelectionOwner *m_selectionOwner; }; diff --git a/traymanager1.cpp b/traymanager1.cpp index 2e52017..1f8941a 100644 --- a/traymanager1.cpp +++ b/traymanager1.cpp @@ -4,10 +4,11 @@ */ #include "traymanager1.h" -#include "traymanagerproxy.h" #include "debug.h" #include "traymanager1adaptor.h" +#include +#include TrayManager1::TrayManager1(QObject *parent) : QObject(parent) , m_adaptor(new TrayManager1Adaptor(this)) @@ -20,15 +21,15 @@ TrayManager1::~TrayManager1() qCDebug(SNIPROXY) << "TrayManager1 destroyed"; } -void TrayManager1::registerIcon(xcb_window_t win, TrayManagerProxy *proxy) +void TrayManager1::registerIcon(xcb_window_t win) { if (m_icons.contains(win)) { qCWarning(SNIPROXY) << "Icon already registered:" << win; return; } - m_icons[win] = proxy; - qCDebug(SNIPROXY) << "Icon registered:" << win << "name:" << proxy->name(); + m_icons[win] = true; + qCDebug(SNIPROXY) << "Icon registered:" << win ;//<< "name:" << proxy->name(); Q_EMIT Added(static_cast(win)); } @@ -66,9 +67,9 @@ TrayList TrayManager1::trayIcons() const return result; } -TrayManagerProxy *TrayManager1::iconProxy(xcb_window_t win) const +bool TrayManager1::haveIcon(xcb_window_t win) const { - return m_icons.value(win, nullptr); + return m_icons.contains(win); } // DBus method implementations @@ -80,17 +81,13 @@ bool TrayManager1::Manage() QString TrayManager1::GetName(uint32_t win) { - auto proxy = m_icons.value(static_cast(win), nullptr); - if (proxy) { - return proxy->name(); - } - return QString(); + auto connection = qGuiApp->nativeInterface()->connection(); + KWindowInfo info(win, NET::WMName | NET::WMIconName); + return info.name(); } void TrayManager1::EnableNotification(uint32_t win, bool enabled) { - auto proxy = m_icons.value(static_cast(win), nullptr); - if (proxy) { - qCDebug(SNIPROXY) << "EnableNotification for" << win << "=" << enabled; - } + // TODO: Implement + qCDebug(SNIPROXY) << "TODO: EnableNotification for" << win << "=" << enabled; } diff --git a/traymanager1.h b/traymanager1.h index 28a2cda..d0f6df7 100644 --- a/traymanager1.h +++ b/traymanager1.h @@ -40,7 +40,7 @@ public: * @param win Window ID of the embedded tray icon * @param proxy Pointer to the TrayManagerProxy managing this icon */ - void registerIcon(xcb_window_t win, TrayManagerProxy *proxy); + void registerIcon(xcb_window_t win); /** * @brief Unregister a tray icon @@ -59,10 +59,7 @@ public: */ TrayList trayIcons() const; - /** - * @return Pointer to TrayManagerProxy for the given window, or nullptr - */ - TrayManagerProxy *iconProxy(xcb_window_t win) const; + bool haveIcon(xcb_window_t win) const; public Q_SLOTS: // DBus methods @@ -79,5 +76,5 @@ Q_SIGNALS: private: TrayManager1Adaptor * m_adaptor; - QHash m_icons; + QHash m_icons; }; diff --git a/traymanagerproxy.cpp b/traymanagerproxy.cpp deleted file mode 100644 index 28705d4..0000000 --- a/traymanagerproxy.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* - Xembed Tray Manager Proxy - holds one embedded window - SPDX-FileCopyrightText: 2015 David Edmundson - SPDX-FileCopyrightText: 2019 Konrad Materka - SPDX-License-Identifier: LGPL-2.1-or-later -*/ - -#include "traymanagerproxy.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "xcbutils.h" -#include "xtestsender.h" -#include "c_ptr.h" -#include -#include - - -#ifdef Status -typedef Status XStatus; -#undef Status -typedef XStatus Status; -#endif - -static uint16_t s_embedSize = 32; -static unsigned int XEMBED_VERSION = 0; - -void xembed_message_send(xcb_window_t towin, long message, long d1, long d2, long d3) -{ - xcb_client_message_event_t ev; - - ev.response_type = XCB_CLIENT_MESSAGE; - ev.window = towin; - ev.format = 32; - ev.data.data32[0] = XCB_CURRENT_TIME; - ev.data.data32[1] = message; - ev.data.data32[2] = d1; - ev.data.data32[3] = d2; - ev.data.data32[4] = d3; - ev.type = Xcb::atoms->xembedAtom; - xcb_send_event(qGuiApp->nativeInterface()->connection(), false, towin, XCB_EVENT_MASK_NO_EVENT, (char *)&ev); -} - -static bool checkWindowOrDescendantWantButtonEvents(xcb_window_t window) -{ - auto connection = qGuiApp->nativeInterface()->connection(); - auto waCookie = xcb_get_window_attributes(connection, window); - UniqueCPointer windowAttributes(xcb_get_window_attributes_reply(connection, waCookie, nullptr)); - if (windowAttributes && windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS) { - return true; - } - if (windowAttributes && windowAttributes->do_not_propagate_mask & XCB_EVENT_MASK_BUTTON_PRESS) { - return false; - } - auto treeCookie = xcb_query_tree(connection, window); - UniqueCPointer tree(xcb_query_tree_reply(connection, treeCookie, nullptr)); - if (!tree) { - return false; - } - std::span children(xcb_query_tree_children(tree.get()), xcb_query_tree_children_length(tree.get())); - return std::ranges::any_of(children, &checkWindowOrDescendantWantButtonEvents); -} - -TrayManagerProxy::TrayManagerProxy(xcb_window_t wid, QObject *parent) - : QObject(parent) - , m_x11Interface(qGuiApp->nativeInterface()) - , m_windowId(wid) - , m_injectMode(Direct) -{ - m_name = getWindowName(); - - auto c = m_x11Interface->connection(); - - // create a container window - auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; - m_containerWid = xcb_generate_id(c); - uint32_t values[3]; - uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - values[0] = screen->black_pixel; - values[1] = true; - values[2] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; - xcb_create_window(c, - XCB_COPY_FROM_PARENT, - m_containerWid, - screen->root, - 0, 0, - s_embedSize, s_embedSize, - 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - screen->root_visual, - mask, - values); - - setActiveForInput(false); - - NETWinInfo wm(c, m_containerWid, screen->root, NET::Properties(), NET::Properties2()); - wm.setOpacity(0); - - xcb_flush(c); - xcb_map_window(c, m_containerWid); - - xcb_reparent_window(c, wid, m_containerWid, 0, 0); - - // Render the embedded window offscreen - xcb_composite_redirect_window(c, wid, XCB_COMPOSITE_REDIRECT_MANUAL); - - xcb_change_save_set(c, XCB_SET_MODE_INSERT, wid); - - // tell client we're embedding it - xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, 0, m_containerWid, XEMBED_VERSION); - - // move window we're embedding - const uint32_t windowMoveConfigVals[2] = {0, 0}; - xcb_configure_window(c, wid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals); - - QSize clientWindowSize = calculateClientWindowSize(); - - // show the embedded window - xcb_map_window(c, wid); - xcb_clear_area(c, 0, wid, 0, 0, clientWindowSize.width(), clientWindowSize.height()); - xcb_flush(c); - - // guess which input injection method to use - auto waCookie = xcb_get_window_attributes(c, wid); - UniqueCPointer windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr)); - if (!checkWindowOrDescendantWantButtonEvents(wid)) { - m_injectMode = XTest; - } - - // First update after delay - QTimer::singleShot(500, this, &TrayManagerProxy::update); -} - -TrayManagerProxy::~TrayManagerProxy() -{ - auto c = m_x11Interface->connection(); - xcb_destroy_window(c, m_containerWid); - xcb_flush(c); -} - -QString TrayManagerProxy::getWindowName() const -{ - auto connection = m_x11Interface->connection(); - KWindowInfo info(m_windowId, NET::WMName | NET::WMIconName); - return info.name(); -} - -void TrayManagerProxy::update() -{ - QImage newImage = getImageNonComposite(); - - if (!newImage.isNull() && newImage != m_lastImage) { - m_lastImage = newImage; - // Icon image changed, could trigger update on TrayManager1 - // This would be handled at a higher level - } - - QTimer::singleShot(100, this, &TrayManagerProxy::update); -} - -void TrayManagerProxy::resizeWindow(const uint16_t width, const uint16_t height) const -{ - auto c = m_x11Interface->connection(); - const uint32_t values[] = {width, height}; - xcb_configure_window(c, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); - xcb_flush(c); -} - -QSize TrayManagerProxy::calculateClientWindowSize() const -{ - auto c = m_x11Interface->connection(); - auto geoCookie = xcb_get_geometry(c, m_windowId); - UniqueCPointer geo(xcb_get_geometry_reply(c, geoCookie, nullptr)); - if (geo) { - return QSize(geo->width, geo->height); - } - return QSize(s_embedSize, s_embedSize); -} - -void TrayManagerProxy::sendClick(uint8_t mouseButton, int x, int y) -{ - if (m_injectMode == XTest) { - // Use XTest helper functions defined in xtestsender.h - sendXTestPressed(m_x11Interface->display(), mouseButton); - sendXTestReleased(m_x11Interface->display(), mouseButton); - return; - } - - auto c = m_x11Interface->connection(); - xcb_button_press_event_t pressEvent{}; - memset(&pressEvent, 0, sizeof(pressEvent)); - pressEvent.response_type = XCB_BUTTON_PRESS; - pressEvent.event = m_windowId; - pressEvent.time = XCB_CURRENT_TIME; - pressEvent.same_screen = 1; - pressEvent.root = DefaultRootWindow(m_x11Interface->display()); - pressEvent.root_x = x; - pressEvent.root_y = y; - pressEvent.event_x = static_cast(x); - pressEvent.event_y = static_cast(y); - pressEvent.child = 0; - pressEvent.state = 0; - pressEvent.detail = mouseButton; - - xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, reinterpret_cast(&pressEvent)); - - xcb_button_release_event_t releaseEvent{}; - memset(&releaseEvent, 0, sizeof(releaseEvent)); - releaseEvent.response_type = XCB_BUTTON_RELEASE; - releaseEvent.event = m_windowId; - releaseEvent.time = XCB_CURRENT_TIME; - releaseEvent.same_screen = 1; - releaseEvent.root = DefaultRootWindow(m_x11Interface->display()); - releaseEvent.root_x = x; - releaseEvent.root_y = y; - releaseEvent.event_x = static_cast(x); - releaseEvent.event_y = static_cast(y); - releaseEvent.child = 0; - releaseEvent.state = 0; - releaseEvent.detail = mouseButton; - - xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast(&releaseEvent)); - xcb_flush(c); -} - -QImage TrayManagerProxy::getImageNonComposite() const -{ - auto c = m_x11Interface->connection(); - - auto geoCookie = xcb_get_geometry(c, m_windowId); - UniqueCPointer geo(xcb_get_geometry_reply(c, geoCookie, nullptr)); - if (!geo) { - return QImage(); - } - // Use xcb_image_get directly on the drawable. Use UINT32_MAX for plane mask. - xcb_image_t *xcbimg = xcb_image_get(c, m_windowId, 0, 0, geo->width, geo->height, UINT32_MAX, XCB_IMAGE_FORMAT_Z_PIXMAP); - if (!xcbimg) { - return QImage(); - } - - return convertFromNative(xcbimg); -} - -bool TrayManagerProxy::isTransparentImage(const QImage &image) const -{ - if (image.format() != QImage::Format_ARGB32) { - return false; - } - - const QRgb *data = reinterpret_cast(image.bits()); - for (int i = 0; i < image.width() * image.height(); ++i) { - if (qAlpha(data[i]) != 0) { - return false; - } - } - return true; -} - -QImage TrayManagerProxy::convertFromNative(xcb_image_t *xcbImage) const -{ - if (!xcbImage) { - return QImage(); - } - - QImage qimage(xcbImage->width, xcbImage->height, QImage::Format_ARGB32); - memcpy(qimage.bits(), xcbImage->data, qimage.sizeInBytes()); - - return qimage; -} - -QPoint TrayManagerProxy::calculateClickPoint() const -{ - return QPoint(s_embedSize / 2, s_embedSize / 2); -} - -void TrayManagerProxy::setActiveForInput(bool active) const -{ - auto c = m_x11Interface->connection(); - auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; - - xcb_rectangle_t rect = {0, 0, 0, 0}; - if (active) { - rect.width = s_embedSize; - rect.height = s_embedSize; - } - - xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_containerWid, 0, 0, 1, &rect); - xcb_flush(c); -} diff --git a/traymanagerproxy.h b/traymanagerproxy.h deleted file mode 100644 index 8a6d8a7..0000000 --- a/traymanagerproxy.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - Xembed Tray Manager Proxy - holds one embedded window - SPDX-FileCopyrightText: 2015 David Edmundson - SPDX-FileCopyrightText: 2019 Konrad Materka - SPDX-License-Identifier: LGPL-2.1-or-later -*/ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -class TrayManagerProxy : public QObject -{ - Q_OBJECT - -public: - explicit TrayManagerProxy(xcb_window_t wid, QObject *parent = nullptr); - ~TrayManagerProxy() override; - - void update(); - void resizeWindow(const uint16_t width, const uint16_t height) const; - - /** - * @return the window id of this item - */ - uint32_t windowId() const { return m_windowId; } - - /** - * @return the name/title of this item - */ - QString name() const { return m_name; } - -private: - enum InjectMode { - Direct, - XTest, - }; - - QSize calculateClientWindowSize() const; - void sendClick(uint8_t mouseButton, int x, int y); - QImage getImageNonComposite() const; - bool isTransparentImage(const QImage &image) const; - QImage convertFromNative(xcb_image_t *xcbImage) const; - QPoint calculateClickPoint() const; - void setActiveForInput(bool active) const; - QString getWindowName() const; - - QNativeInterface::QX11Application *m_x11Interface = nullptr; - xcb_window_t m_windowId; - xcb_window_t m_containerWid; - uint32_t m_damageId = 0; - InjectMode m_injectMode; - QString m_name; - QImage m_lastImage; -};