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;
-};