Compare commits
13 Commits
dbb755c976
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b7bcf428ee | |||
| cb3830e6c7 | |||
| 16c90b6d25 | |||
| 93daa1af0f | |||
| 866b81a3dc | |||
| faf5b43e2b | |||
| b0df2b9e27 | |||
| b8f5165fc4 | |||
| cc79c9b9e2 | |||
| 0be1e76ba2 | |||
| 902a5ce5dd | |||
| 459506acd0 | |||
| 170b161e30 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
build/
|
build/
|
||||||
.cache/
|
.cache/
|
||||||
|
.vscode/
|
||||||
@@ -8,60 +8,28 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) # ensure adapter class can include related hea
|
|||||||
|
|
||||||
find_package(Qt6 6.8 CONFIG REQUIRED COMPONENTS DBus)
|
find_package(Qt6 6.8 CONFIG REQUIRED COMPONENTS DBus)
|
||||||
find_package(ECM REQUIRED NO_MODULE)
|
find_package(ECM REQUIRED NO_MODULE)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||||
include(ECMQtDeclareLoggingCategory)
|
|
||||||
include(KDEInstallDirs)
|
include(KDEInstallDirs)
|
||||||
include(ECMConfiguredInstall)
|
|
||||||
|
|
||||||
find_package(KF6 6.6 REQUIRED COMPONENTS
|
find_package(KF6WindowSystem 6.6 REQUIRED)
|
||||||
WindowSystem)
|
|
||||||
|
|
||||||
find_package(XCB
|
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11 xcb xcb-image xcb-damage xcb-composite xcb-xfixes xcb-util xcb-shape xtst xcb-xtest xcb-res xcb-ewmh)
|
||||||
REQUIRED COMPONENTS
|
|
||||||
XCB
|
|
||||||
XFIXES
|
|
||||||
DAMAGE
|
|
||||||
COMPOSITE
|
|
||||||
RANDR
|
|
||||||
SHM
|
|
||||||
UTIL
|
|
||||||
IMAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
set(XCB_LIBS
|
|
||||||
XCB::XCB
|
|
||||||
XCB::XFIXES
|
|
||||||
XCB::DAMAGE
|
|
||||||
XCB::COMPOSITE
|
|
||||||
XCB::RANDR
|
|
||||||
XCB::SHM
|
|
||||||
XCB::UTIL
|
|
||||||
XCB::IMAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
set(XEMBED_SNI_PROXY_SOURCES
|
set(XEMBED_SNI_PROXY_SOURCES
|
||||||
main.cpp
|
main.cpp
|
||||||
fdoselectionmanager.cpp fdoselectionmanager.h
|
fdoselectionmanager.cpp fdoselectionmanager.h
|
||||||
traymanager1.cpp traymanager1.h
|
traymanager1.cpp traymanager1.h
|
||||||
traymanagerproxy.cpp traymanagerproxy.h
|
util.cpp util.h
|
||||||
xtestsender.cpp xtestsender.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(
|
set_source_files_properties(
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/org.deepin.dde.TrayManager1.xml
|
${CMAKE_CURRENT_SOURCE_DIR}/api/dbus/org.deepin.dde.TrayManager1.xml
|
||||||
PROPERTIES INCLUDE traylist.h
|
PROPERTIES INCLUDE api/types/traylist.h
|
||||||
CLASSNAME TrayManager
|
CLASSNAME TrayManager
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES org.deepin.dde.TrayManager1.xml traymanager1.h TrayManager1)
|
qt_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES api/dbus/org.deepin.dde.TrayManager1.xml traymanager1.h TrayManager1)
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(XEMBED_SNI_PROXY_SOURCES HEADER debug.h
|
|
||||||
IDENTIFIER SNIPROXY
|
|
||||||
CATEGORY_NAME dde.xembedsniproxy
|
|
||||||
DEFAULT_SEVERITY Info
|
|
||||||
DESCRIPTION "xembed sni proxy"
|
|
||||||
EXPORT PLASMAWORKSPACE
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(xembed-traymanager-proxy ${XEMBED_SNI_PROXY_SOURCES})
|
add_executable(xembed-traymanager-proxy ${XEMBED_SNI_PROXY_SOURCES})
|
||||||
set_property(TARGET xembed-traymanager-proxy PROPERTY AUTOMOC ON)
|
set_property(TARGET xembed-traymanager-proxy PROPERTY AUTOMOC ON)
|
||||||
@@ -74,11 +42,8 @@ target_link_libraries(xembed-traymanager-proxy
|
|||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::DBus
|
Qt::DBus
|
||||||
KF6::WindowSystem
|
KF6::WindowSystem
|
||||||
${XCB_LIBS}
|
PkgConfig::X11
|
||||||
X11::Xtst
|
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS xembed-traymanager-proxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
install(TARGETS xembed-traymanager-proxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
install(FILES xembedsniproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
|
install(FILES xembedsniproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
|
||||||
|
|
||||||
ecm_install_configured_files(INPUT plasma-xembedsniproxy.service.in @ONLY DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR})
|
|
||||||
|
|||||||
36
Readme.md
36
Readme.md
@@ -1,34 +1,24 @@
|
|||||||
## Note
|
## XEmbed TrayManager1 Proxy
|
||||||
|
|
||||||
This project is modified from KDE's xembed-sni-proxy project, extracted from plasma-workspace.
|
The goal of this project is to make xembed system trays available in DDE Treeland (wayland) session.
|
||||||
|
|
||||||
##XEmbed SNI Proxy
|
This is to allow legacy apps (xchat, pidgin, tuxguitar) etc. system trays[1] available in DDE Treeland which only supports StatusNotifierItem [2].
|
||||||
|
|
||||||
The goal of this project is to make xembed system trays available in Plasma.
|
> [!NOTE]
|
||||||
|
> The initial commit was a minimal KF6 dependency version of KDE's `xembed-sni-proxy` project, extracted from `plasma-workspace`.
|
||||||
|
> Currently this project is a standalone project that provides Xembed tray information on TrayManager1 D-Bus, so `dde-tray-loader` could consume it and provide Xembed tray icons.
|
||||||
|
> This project will be integrated into `dde-tray-loader` project.
|
||||||
|
|
||||||
This is to allow legacy apps (xchat, pidgin, tuxguitar) etc. system trays[1] available in Plasma which only supports StatusNotifierItem [2].
|
This tool can be used on a Wayland session with `wayland` QPA, `xcb` QPA can also be used but it's not mandatory, but either way it's required to have a X connection (via X11 or Xwayland).
|
||||||
|
|
||||||
Ideally we also want this to work in an xwayland session, making X system tray icons available even when plasmashell only has a wayland connection.
|
|
||||||
|
|
||||||
This project should be portable onto all other DEs that speak SNI.
|
|
||||||
|
|
||||||
##How it works (in theory)
|
|
||||||
|
|
||||||
* We register a window as a system tray container
|
|
||||||
* We render embedded windows composited offscreen
|
|
||||||
* We render contents into an image and send this over DBus via the SNI protocol
|
|
||||||
* XDamage events trigger a repaint
|
|
||||||
* Activate and context menu events are replyed via X send event into the embedded container as left and right clicks
|
|
||||||
|
|
||||||
There are a few extra hacks in the real code to deal with some toolkits being awkward.
|
|
||||||
|
|
||||||
## Build instructions
|
## Build instructions
|
||||||
|
|
||||||
cmake .
|
```shell
|
||||||
make
|
$ cmake -Bbuild .
|
||||||
sudo make install
|
$ cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
After building, run `xembedsniproxy`.
|
After building, run `xembed-traymanager-proxy`.
|
||||||
|
|
||||||
[1] http://standards.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
|
[1] http://standards.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
|
||||||
[2] http://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
|
[2] http://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
|
||||||
|
|||||||
207
doc.md
Normal file
207
doc.md
Normal file
@@ -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
|
||||||
|
<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 属性
|
||||||
|
└─ 展示图标
|
||||||
|
```
|
||||||
|
|
||||||
|
**构建和部署**
|
||||||
|
|
||||||
|
```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` 包含安装规则,我可以继续操作。
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
Registers as a embed container
|
Registers as a embed container
|
||||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
||||||
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
||||||
|
SPDX-FileCopyrightText: 2025 Wang Zichong <wangzichong@deepin.org>
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
*/
|
*/
|
||||||
#include "fdoselectionmanager.h"
|
#include "fdoselectionmanager.h"
|
||||||
|
|
||||||
#include "debug.h"
|
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
#include <KSelectionOwner>
|
#include <KSelectionOwner>
|
||||||
|
|
||||||
@@ -20,19 +20,22 @@
|
|||||||
#include <xcb/xcb_event.h>
|
#include <xcb/xcb_event.h>
|
||||||
|
|
||||||
#include "traymanager1.h"
|
#include "traymanager1.h"
|
||||||
#include "traymanagerproxy.h"
|
#include "c_ptr.h"
|
||||||
#include "xcbutils.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
using Util = tray::Util;
|
||||||
|
|
||||||
#define SYSTEM_TRAY_REQUEST_DOCK 0
|
#define SYSTEM_TRAY_REQUEST_DOCK 0
|
||||||
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
|
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
|
||||||
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
|
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
|
||||||
|
|
||||||
FdoSelectionManager::FdoSelectionManager()
|
Q_LOGGING_CATEGORY(SELECTIONMGR, "org.deepin.dde.trayloader.selectionmgr")
|
||||||
: QObject()
|
|
||||||
, m_x11Interface(qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
|
FdoSelectionManager::FdoSelectionManager(QObject *parent)
|
||||||
, m_selectionOwner(new KSelectionOwner(Xcb::atoms->selectionAtom, -1, this))
|
: QObject(parent)
|
||||||
|
, m_selectionOwner(new KSelectionOwner(UTIL->getAtomFromDisplay("_NET_SYSTEM_TRAY"), UTIL->getX11Connection(), UTIL->getRootWindow(), this))
|
||||||
{
|
{
|
||||||
qDebug(SNIPROXY) << "starting";
|
qCDebug(SELECTIONMGR) << "starting";
|
||||||
|
|
||||||
// we may end up calling QCoreApplication::quit() in this method, at which point we need the event loop running
|
// we may end up calling QCoreApplication::quit() in this method, at which point we need the event loop running
|
||||||
QTimer::singleShot(0, this, &FdoSelectionManager::init);
|
QTimer::singleShot(0, this, &FdoSelectionManager::init);
|
||||||
@@ -40,14 +43,14 @@ FdoSelectionManager::FdoSelectionManager()
|
|||||||
|
|
||||||
FdoSelectionManager::~FdoSelectionManager()
|
FdoSelectionManager::~FdoSelectionManager()
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "closing";
|
qCDebug(SELECTIONMGR) << "closing";
|
||||||
m_selectionOwner->release();
|
m_selectionOwner->release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FdoSelectionManager::init()
|
void FdoSelectionManager::init()
|
||||||
{
|
{
|
||||||
// load damage extension
|
// load damage extension
|
||||||
xcb_connection_t *c = m_x11Interface->connection();
|
xcb_connection_t *c = Util::instance()->getX11Connection();
|
||||||
xcb_prefetch_extension_data(c, &xcb_damage_id);
|
xcb_prefetch_extension_data(c, &xcb_damage_id);
|
||||||
const auto *reply = xcb_get_extension_data(c, &xcb_damage_id);
|
const auto *reply = xcb_get_extension_data(c, &xcb_damage_id);
|
||||||
if (reply && reply->present) {
|
if (reply && reply->present) {
|
||||||
@@ -55,7 +58,7 @@ void FdoSelectionManager::init()
|
|||||||
xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
|
xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
|
||||||
} else {
|
} else {
|
||||||
// no XDamage means
|
// no XDamage means
|
||||||
qCCritical(SNIPROXY) << "could not load damage extension. Quitting";
|
qCCritical(SELECTIONMGR) << "could not load damage extension. Quitting";
|
||||||
qApp->exit(-1);
|
qApp->exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,14 +67,18 @@ void FdoSelectionManager::init()
|
|||||||
connect(m_selectionOwner, &KSelectionOwner::claimedOwnership, this, &FdoSelectionManager::onClaimedOwnership);
|
connect(m_selectionOwner, &KSelectionOwner::claimedOwnership, this, &FdoSelectionManager::onClaimedOwnership);
|
||||||
connect(m_selectionOwner, &KSelectionOwner::failedToClaimOwnership, this, &FdoSelectionManager::onFailedToClaimOwnership);
|
connect(m_selectionOwner, &KSelectionOwner::failedToClaimOwnership, this, &FdoSelectionManager::onFailedToClaimOwnership);
|
||||||
connect(m_selectionOwner, &KSelectionOwner::lostOwnership, this, &FdoSelectionManager::onLostOwnership);
|
connect(m_selectionOwner, &KSelectionOwner::lostOwnership, this, &FdoSelectionManager::onLostOwnership);
|
||||||
m_selectionOwner->claim(false);
|
m_selectionOwner->claim(true);
|
||||||
|
|
||||||
|
connect(m_trayManager, &TrayManager1::reclainRequested, this, [this](){
|
||||||
|
m_selectionOwner->claim(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FdoSelectionManager::addDamageWatch(xcb_window_t client)
|
bool FdoSelectionManager::addDamageWatch(xcb_window_t client)
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "adding damage watch for " << client;
|
qCDebug(SELECTIONMGR) << "adding damage watch for " << client;
|
||||||
|
|
||||||
xcb_connection_t *c = m_x11Interface->connection();
|
xcb_connection_t *c = Util::instance()->getX11Connection();
|
||||||
const auto attribsCookie = xcb_get_window_attributes_unchecked(c, client);
|
const auto attribsCookie = xcb_get_window_attributes_unchecked(c, client);
|
||||||
|
|
||||||
const auto damageId = xcb_generate_id(c);
|
const auto damageId = xcb_generate_id(c);
|
||||||
@@ -114,7 +121,7 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m
|
|||||||
const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
|
const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
|
||||||
if (responseType == XCB_CLIENT_MESSAGE) {
|
if (responseType == XCB_CLIENT_MESSAGE) {
|
||||||
const auto ce = reinterpret_cast<xcb_client_message_event_t *>(ev);
|
const auto ce = reinterpret_cast<xcb_client_message_event_t *>(ev);
|
||||||
if (ce->type == Xcb::atoms->opcodeAtom) {
|
if (ce->type == UTIL->getAtomByName("_NET_SYSTEM_TRAY_OPCODE")) {
|
||||||
switch (ce->data.data32[1]) {
|
switch (ce->data.data32[1]) {
|
||||||
case SYSTEM_TRAY_REQUEST_DOCK:
|
case SYSTEM_TRAY_REQUEST_DOCK:
|
||||||
dock(ce->data.data32[2]);
|
dock(ce->data.data32[2]);
|
||||||
@@ -122,31 +129,27 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (responseType == XCB_UNMAP_NOTIFY) {
|
} else if (responseType == XCB_UNMAP_NOTIFY) {
|
||||||
const auto unmappedWId = reinterpret_cast<xcb_unmap_notify_event_t *>(ev)->window;
|
// const auto unmappedWId = reinterpret_cast<xcb_unmap_notify_event_t *>(ev)->window;
|
||||||
if (m_proxies.contains(unmappedWId)) {
|
// if (m_proxies.contains(unmappedWId)) {
|
||||||
undock(unmappedWId);
|
// undock(unmappedWId);
|
||||||
}
|
// }
|
||||||
} else if (responseType == XCB_DESTROY_NOTIFY) {
|
} else if (responseType == XCB_DESTROY_NOTIFY) {
|
||||||
const auto destroyedWId = reinterpret_cast<xcb_destroy_notify_event_t *>(ev)->window;
|
const auto destroyedWId = reinterpret_cast<xcb_destroy_notify_event_t *>(ev)->window;
|
||||||
if (m_proxies.contains(destroyedWId)) {
|
if (m_trayManager->haveIcon(destroyedWId)) {
|
||||||
undock(destroyedWId);
|
undock(destroyedWId);
|
||||||
}
|
}
|
||||||
} else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
|
} else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
|
||||||
const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable;
|
const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable;
|
||||||
const auto tmProxy = m_proxies.value(damagedWId);
|
m_trayManager->notifyIconChanged(damagedWId);
|
||||||
if (tmProxy) {
|
|
||||||
tmProxy->update();
|
|
||||||
xcb_damage_subtract(m_x11Interface->connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE);
|
|
||||||
}
|
|
||||||
} else if (responseType == XCB_CONFIGURE_REQUEST) {
|
} else if (responseType == XCB_CONFIGURE_REQUEST) {
|
||||||
const auto event = reinterpret_cast<xcb_configure_request_event_t *>(ev);
|
// const auto event = reinterpret_cast<xcb_configure_request_event_t *>(ev);
|
||||||
const auto tmProxy = m_proxies.value(event->window);
|
// const auto tmProxy = m_proxies.value(event->window);
|
||||||
if (tmProxy) {
|
// if (tmProxy) {
|
||||||
// The embedded window tries to move or resize. Ignore move, handle resize only.
|
// // 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)) {
|
// if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
|
||||||
tmProxy->resizeWindow(event->width, event->height);
|
// tmProxy->resizeWindow(event->width, event->height);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -154,43 +157,39 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m
|
|||||||
|
|
||||||
void FdoSelectionManager::dock(xcb_window_t winId)
|
void FdoSelectionManager::dock(xcb_window_t winId)
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "trying to dock window " << winId;
|
Q_CHECK_PTR(m_trayManager);
|
||||||
|
qCDebug(SELECTIONMGR) << "trying to dock window " << winId;
|
||||||
|
|
||||||
if (m_proxies.contains(winId)) {
|
if (m_trayManager->haveIcon(winId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addDamageWatch(winId)) {
|
if (addDamageWatch(winId)) {
|
||||||
auto proxy = new TrayManagerProxy(winId, this);
|
|
||||||
m_proxies[winId] = proxy;
|
|
||||||
|
|
||||||
// Register with TrayManager1 if available
|
// Register with TrayManager1 if available
|
||||||
if (m_trayManager) {
|
m_trayManager->registerIcon(winId);
|
||||||
m_trayManager->registerIcon(winId, proxy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FdoSelectionManager::undock(xcb_window_t winId)
|
void FdoSelectionManager::undock(xcb_window_t winId)
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "trying to undock window " << winId;
|
Q_CHECK_PTR(m_trayManager);
|
||||||
|
qCDebug(SELECTIONMGR) << "trying to undock window " << winId;
|
||||||
|
|
||||||
if (!m_proxies.contains(winId)) {
|
if (!m_trayManager->haveIcon(winId)) {
|
||||||
|
qCDebug(SELECTIONMGR) << "failed to find winId to undock:" << winId;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unregister from TrayManager1 if available
|
// Unregister from TrayManager1 if available
|
||||||
if (m_trayManager) {
|
|
||||||
m_trayManager->unregisterIcon(winId);
|
m_trayManager->unregisterIcon(winId);
|
||||||
}
|
|
||||||
|
|
||||||
m_proxies[winId]->deleteLater();
|
// m_proxies[winId]->deleteLater();
|
||||||
m_proxies.remove(winId);
|
// m_proxies.remove(winId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FdoSelectionManager::onClaimedOwnership()
|
void FdoSelectionManager::onClaimedOwnership()
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "Manager selection claimed";
|
qCDebug(SELECTIONMGR) << "Manager selection claimed";
|
||||||
|
|
||||||
initTrayManager();
|
initTrayManager();
|
||||||
setSystemTrayVisual();
|
setSystemTrayVisual();
|
||||||
@@ -198,19 +197,17 @@ void FdoSelectionManager::onClaimedOwnership()
|
|||||||
|
|
||||||
void FdoSelectionManager::onFailedToClaimOwnership()
|
void FdoSelectionManager::onFailedToClaimOwnership()
|
||||||
{
|
{
|
||||||
qCWarning(SNIPROXY) << "failed to claim ownership of Systray Manager";
|
qCWarning(SELECTIONMGR) << "failed to claim ownership of Systray Manager";
|
||||||
qApp->exit(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FdoSelectionManager::onLostOwnership()
|
void FdoSelectionManager::onLostOwnership()
|
||||||
{
|
{
|
||||||
qCWarning(SNIPROXY) << "lost ownership of Systray Manager";
|
qCWarning(SELECTIONMGR) << "lost ownership of Systray Manager";
|
||||||
qApp->exit(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FdoSelectionManager::setSystemTrayVisual()
|
void FdoSelectionManager::setSystemTrayVisual()
|
||||||
{
|
{
|
||||||
xcb_connection_t *c = m_x11Interface->connection();
|
xcb_connection_t *c = Util::instance()->getX11Connection();
|
||||||
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
||||||
auto trayVisual = screen->root_visual;
|
auto trayVisual = screen->root_visual;
|
||||||
xcb_depth_iterator_t depth_iterator = xcb_screen_allowed_depths_iterator(screen);
|
xcb_depth_iterator_t depth_iterator = xcb_screen_allowed_depths_iterator(screen);
|
||||||
@@ -236,7 +233,7 @@ void FdoSelectionManager::setSystemTrayVisual()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_selectionOwner->ownerWindow(), Xcb::atoms->visualAtom, XCB_ATOM_VISUALID, 32, 1, &trayVisual);
|
xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_selectionOwner->ownerWindow(), UTIL->getAtomByName("_NET_SYSTEM_TRAY_VISUAL"), XCB_ATOM_VISUALID, 32, 1, &trayVisual);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FdoSelectionManager::initTrayManager()
|
void FdoSelectionManager::initTrayManager()
|
||||||
@@ -257,7 +254,7 @@ void FdoSelectionManager::initTrayManager()
|
|||||||
QStringLiteral("org.deepin.dde.TrayManager1")
|
QStringLiteral("org.deepin.dde.TrayManager1")
|
||||||
);
|
);
|
||||||
|
|
||||||
qCDebug(SNIPROXY) << "TrayManager1 DBus interface registered";
|
qCDebug(SELECTIONMGR) << "TrayManager1 DBus interface registered";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class FdoSelectionManager : public QObject, public QAbstractNativeEventFilter
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FdoSelectionManager();
|
FdoSelectionManager(QObject *parent = nullptr);
|
||||||
~FdoSelectionManager() override;
|
~FdoSelectionManager() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -42,12 +42,11 @@ private:
|
|||||||
void setSystemTrayVisual();
|
void setSystemTrayVisual();
|
||||||
void initTrayManager();
|
void initTrayManager();
|
||||||
|
|
||||||
QNativeInterface::QX11Application *m_x11Interface = nullptr;
|
|
||||||
TrayManager1 *m_trayManager = nullptr;
|
TrayManager1 *m_trayManager = nullptr;
|
||||||
|
|
||||||
uint8_t m_damageEventBase;
|
uint8_t m_damageEventBase;
|
||||||
|
|
||||||
QHash<xcb_window_t, u_int32_t> m_damageWatches;
|
QHash<xcb_window_t, u_int32_t> m_damageWatches;
|
||||||
QHash<xcb_window_t, TrayManagerProxy *> m_proxies;
|
// QHash<xcb_window_t, TrayManagerProxy *> m_proxies;
|
||||||
KSelectionOwner *m_selectionOwner;
|
KSelectionOwner *m_selectionOwner;
|
||||||
};
|
};
|
||||||
|
|||||||
24
main.cpp
24
main.cpp
@@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
#include "fdoselectionmanager.h"
|
#include "fdoselectionmanager.h"
|
||||||
|
|
||||||
#include "debug.h"
|
#include "util.h"
|
||||||
#include "xcbutils.h"
|
|
||||||
|
|
||||||
#ifdef None
|
#ifdef None
|
||||||
#ifndef FIXX11H_None
|
#ifndef FIXX11H_None
|
||||||
@@ -26,34 +25,33 @@ inline constexpr XID None = XNone;
|
|||||||
|
|
||||||
#include <KWindowSystem>
|
#include <KWindowSystem>
|
||||||
|
|
||||||
namespace Xcb
|
using Util = tray::Util;
|
||||||
{
|
|
||||||
Xcb::Atoms *atoms;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
// the whole point of this is to interact with X, if we are in any other session, force trying to connect to X
|
// We will use the X connection managed by UTIL, we don't really care if the tool itself is
|
||||||
// if the QPA can't load xcb, this app is useless anyway.
|
// running with xcb or wayland QPA.
|
||||||
qputenv("QT_QPA_PLATFORM", "xcb");
|
// We'll use wayland QPA for testing on DDE, the following code uses dde-tray-loader's wayland
|
||||||
|
// display, thus we can ensure our X events are not from QPA.
|
||||||
|
qputenv("QT_QPA_PLATFORM", "wayland");
|
||||||
|
qputenv("WAYLAND_DISPLAY", "dockplugin");
|
||||||
|
qputenv("QT_WAYLAND_SHELL_INTEGRATION", "plugin-shell");
|
||||||
|
// qputenv("QT_QPA_PLATFORM", "xcb");
|
||||||
|
|
||||||
QGuiApplication::setDesktopSettingsAware(false);
|
QGuiApplication::setDesktopSettingsAware(false);
|
||||||
QCoreApplication::setAttribute(Qt::AA_DisableSessionManager);
|
QCoreApplication::setAttribute(Qt::AA_DisableSessionManager);
|
||||||
|
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
if (!KWindowSystem::isPlatformX11()) {
|
if (!UTIL->isXAvaliable()) {
|
||||||
qFatal("xembed-traymanager-proxy requires X11. Aborting");
|
qFatal("xembed-traymanager-proxy requires X11. Aborting");
|
||||||
}
|
}
|
||||||
|
|
||||||
app.setQuitOnLastWindowClosed(false);
|
app.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
Xcb::atoms = new Xcb::Atoms();
|
|
||||||
|
|
||||||
FdoSelectionManager manager;
|
FdoSelectionManager manager;
|
||||||
|
|
||||||
auto rc = app.exec();
|
auto rc = app.exec();
|
||||||
|
|
||||||
delete Xcb::atoms;
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
|
||||||
<node>
|
|
||||||
<!-- This is a minimally cut down version of the interface only implementing the
|
|
||||||
methods and properties used by xembedsniproxy -->
|
|
||||||
<interface name="org.kde.StatusNotifierItem">
|
|
||||||
|
|
||||||
<property name="Category" type="s" access="read"/>
|
|
||||||
<property name="Id" type="s" access="read"/>
|
|
||||||
<property name="Title" type="s" access="read"/>
|
|
||||||
<property name="Status" type="s" access="read"/>
|
|
||||||
<property name="WindowId" type="i" access="read"/>
|
|
||||||
|
|
||||||
<property name="ItemIsMenu" type="b" access="read"/>
|
|
||||||
|
|
||||||
|
|
||||||
<property name="IconPixmap" type="(iiay)" access="read">
|
|
||||||
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<!-- interaction: the systemtray wants the application to do something -->
|
|
||||||
<method name="ContextMenu">
|
|
||||||
<!-- we're passing the coordinates of the icon, so the app knows where to put the popup window -->
|
|
||||||
<arg name="x" type="i" direction="in"/>
|
|
||||||
<arg name="y" type="i" direction="in"/>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="Activate">
|
|
||||||
<arg name="x" type="i" direction="in"/>
|
|
||||||
<arg name="y" type="i" direction="in"/>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="SecondaryActivate">
|
|
||||||
<arg name="x" type="i" direction="in"/>
|
|
||||||
<arg name="y" type="i" direction="in"/>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="Scroll">
|
|
||||||
<arg name="delta" type="i" direction="in"/>
|
|
||||||
<arg name="orientation" type="s" direction="in"/>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<!-- Signals: the client wants to change something in the status-->
|
|
||||||
<signal name="NewTitle">
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="NewIcon">
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="NewAttentionIcon">
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="NewOverlayIcon">
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="NewToolTip">
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="NewStatus">
|
|
||||||
<arg name="status" type="s"/>
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
</interface>
|
|
||||||
</node>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
|
||||||
<node>
|
|
||||||
<interface name="org.kde.StatusNotifierWatcher">
|
|
||||||
|
|
||||||
<!-- methods -->
|
|
||||||
<method name="RegisterStatusNotifierItem">
|
|
||||||
<arg name="service" type="s" direction="in"/>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
<method name="RegisterStatusNotifierHost">
|
|
||||||
<arg name="service" type="s" direction="in"/>
|
|
||||||
</method>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- properties -->
|
|
||||||
|
|
||||||
<property name="RegisteredStatusNotifierItems" type="as" access="read">
|
|
||||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
|
|
||||||
|
|
||||||
<property name="ProtocolVersion" type="i" access="read"/>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- signals -->
|
|
||||||
|
|
||||||
<signal name="StatusNotifierItemRegistered">
|
|
||||||
<arg type="s"/>
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="StatusNotifierItemUnregistered">
|
|
||||||
<arg type="s"/>
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="StatusNotifierHostRegistered">
|
|
||||||
</signal>
|
|
||||||
|
|
||||||
<signal name="StatusNotifierHostUnregistered">
|
|
||||||
</signal>
|
|
||||||
</interface>
|
|
||||||
</node>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Handle legacy xembed system tray icons
|
|
||||||
PartOf=graphical-session.target
|
|
||||||
After=plasma-core.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/xembedsniproxy
|
|
||||||
Restart=on-failure
|
|
||||||
Type=simple
|
|
||||||
Slice=background.slice
|
|
||||||
TimeoutSec=5sec
|
|
||||||
@@ -1,34 +1,41 @@
|
|||||||
/*
|
// Deepin DDE TrayManager1 implementation
|
||||||
Deepin DDE TrayManager1 implementation
|
//
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
|
||||||
*/
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "traymanager1.h"
|
#include "traymanager1.h"
|
||||||
#include "traymanagerproxy.h"
|
|
||||||
#include "debug.h"
|
|
||||||
#include "traymanager1adaptor.h"
|
#include "traymanager1adaptor.h"
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <KWindowInfo>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(TRAYMGR, "org.deepin.dde.trayloader.traymgr")
|
||||||
|
|
||||||
TrayManager1::TrayManager1(QObject *parent)
|
TrayManager1::TrayManager1(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_adaptor(new TrayManager1Adaptor(this))
|
, m_adaptor(new TrayManager1Adaptor(this))
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "TrayManager1 created";
|
qCDebug(TRAYMGR) << "TrayManager1 created";
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayManager1::~TrayManager1()
|
TrayManager1::~TrayManager1()
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "TrayManager1 destroyed";
|
qCDebug(TRAYMGR) << "TrayManager1 destroyed";
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrayManager1::registerIcon(xcb_window_t win, TrayManagerProxy *proxy)
|
void TrayManager1::registerIcon(xcb_window_t win)
|
||||||
{
|
{
|
||||||
if (m_icons.contains(win)) {
|
if (m_icons.contains(win)) {
|
||||||
qCWarning(SNIPROXY) << "Icon already registered:" << win;
|
qCWarning(TRAYMGR) << "Icon already registered:" << win;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_icons[win] = proxy;
|
m_icons[win] = true;
|
||||||
qCDebug(SNIPROXY) << "Icon registered:" << win << "name:" << proxy->name();
|
qCDebug(TRAYMGR) << "Icon registered:" << win ;//<< "name:" << proxy->name();
|
||||||
|
|
||||||
Q_EMIT Added(static_cast<uint32_t>(win));
|
Q_EMIT Added(static_cast<uint32_t>(win));
|
||||||
}
|
}
|
||||||
@@ -36,12 +43,12 @@ void TrayManager1::registerIcon(xcb_window_t win, TrayManagerProxy *proxy)
|
|||||||
void TrayManager1::unregisterIcon(xcb_window_t win)
|
void TrayManager1::unregisterIcon(xcb_window_t win)
|
||||||
{
|
{
|
||||||
if (!m_icons.contains(win)) {
|
if (!m_icons.contains(win)) {
|
||||||
qCWarning(SNIPROXY) << "Icon not found for removal:" << win;
|
qCWarning(TRAYMGR) << "Icon not found for removal:" << win;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_icons.remove(win);
|
m_icons.remove(win);
|
||||||
qCDebug(SNIPROXY) << "Icon unregistered:" << win;
|
qCDebug(TRAYMGR) << "Icon unregistered:" << win;
|
||||||
|
|
||||||
Q_EMIT Removed(static_cast<uint32_t>(win));
|
Q_EMIT Removed(static_cast<uint32_t>(win));
|
||||||
}
|
}
|
||||||
@@ -52,7 +59,12 @@ void TrayManager1::notifyIconChanged(xcb_window_t win)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(SNIPROXY) << "Icon changed:" << win;
|
if (!m_icons[win]) {
|
||||||
|
qCDebug(TRAYMGR) << "EnableNotification is false, not sending changed signal for:" << win;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(TRAYMGR) << "Icon changed:" << win;
|
||||||
Q_EMIT Changed(static_cast<uint32_t>(win));
|
Q_EMIT Changed(static_cast<uint32_t>(win));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,31 +78,32 @@ TrayList TrayManager1::trayIcons() const
|
|||||||
return result;
|
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
|
// DBus method implementations
|
||||||
bool TrayManager1::Manage()
|
bool TrayManager1::Manage()
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "Manage() called via DBus";
|
qCDebug(TRAYMGR) << "Manage() called via DBus";
|
||||||
|
emit reclainRequested();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TrayManager1::GetName(uint32_t win)
|
QString TrayManager1::GetName(uint32_t win)
|
||||||
{
|
{
|
||||||
auto proxy = m_icons.value(static_cast<xcb_window_t>(win), nullptr);
|
using Util = tray::Util;
|
||||||
if (proxy) {
|
return UTIL->getX11WindowName(win);
|
||||||
return proxy->name();
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrayManager1::EnableNotification(uint32_t win, bool enabled)
|
void TrayManager1::EnableNotification(uint32_t win, bool enabled)
|
||||||
{
|
{
|
||||||
auto proxy = m_icons.value(static_cast<xcb_window_t>(win), nullptr);
|
if (!m_icons.contains(win)) {
|
||||||
if (proxy) {
|
return;
|
||||||
qCDebug(SNIPROXY) << "EnableNotification for" << win << "=" << enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_icons[win] = enabled;
|
||||||
|
|
||||||
|
qCDebug(TRAYMGR) << "EnableNotification for" << win << "=" << enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/*
|
// Deepin DDE TrayManager1 implementation
|
||||||
Deepin DDE TrayManager1 implementation
|
//
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
|
||||||
*/
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ public:
|
|||||||
* @param win Window ID of the embedded tray icon
|
* @param win Window ID of the embedded tray icon
|
||||||
* @param proxy Pointer to the TrayManagerProxy managing this 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
|
* @brief Unregister a tray icon
|
||||||
@@ -59,10 +60,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
TrayList trayIcons() const;
|
TrayList trayIcons() const;
|
||||||
|
|
||||||
/**
|
bool haveIcon(xcb_window_t win) const;
|
||||||
* @return Pointer to TrayManagerProxy for the given window, or nullptr
|
|
||||||
*/
|
|
||||||
TrayManagerProxy *iconProxy(xcb_window_t win) const;
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
// DBus methods
|
// DBus methods
|
||||||
@@ -77,7 +75,9 @@ Q_SIGNALS:
|
|||||||
void Changed(uint32_t id);
|
void Changed(uint32_t id);
|
||||||
void Inited();
|
void Inited();
|
||||||
|
|
||||||
|
void reclainRequested();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TrayManager1Adaptor * m_adaptor;
|
TrayManager1Adaptor * m_adaptor;
|
||||||
QHash<xcb_window_t, TrayManagerProxy *> m_icons;
|
QHash<xcb_window_t, bool> m_icons; // <winid, enableNotify>
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
/*
|
|
||||||
Xembed Tray Manager Proxy - holds one embedded window
|
|
||||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "traymanagerproxy.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <xcb/xcb_atom.h>
|
|
||||||
#include <xcb/xcb_event.h>
|
|
||||||
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QBitmap>
|
|
||||||
#include <QDBusConnection>
|
|
||||||
#include <QDBusPendingCall>
|
|
||||||
#include <QDBusPendingCallWatcher>
|
|
||||||
|
|
||||||
#include <KWindowInfo>
|
|
||||||
#include <KWindowSystem>
|
|
||||||
#include <netwm.h>
|
|
||||||
|
|
||||||
#include "xcbutils.h"
|
|
||||||
#include "xtestsender.h"
|
|
||||||
#include "c_ptr.h"
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
|
|
||||||
#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<QNativeInterface::QX11Application>()->connection(), false, towin, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool checkWindowOrDescendantWantButtonEvents(xcb_window_t window)
|
|
||||||
{
|
|
||||||
auto connection = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
|
|
||||||
auto waCookie = xcb_get_window_attributes(connection, window);
|
|
||||||
UniqueCPointer<xcb_get_window_attributes_reply_t> 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<xcb_query_tree_reply_t> tree(xcb_query_tree_reply(connection, treeCookie, nullptr));
|
|
||||||
if (!tree) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::span<xcb_window_t> 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<QNativeInterface::QX11Application>())
|
|
||||||
, 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<xcb_get_window_attributes_reply_t> 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<xcb_get_geometry_reply_t> 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<int16_t>(x);
|
|
||||||
pressEvent.event_y = static_cast<int16_t>(y);
|
|
||||||
pressEvent.child = 0;
|
|
||||||
pressEvent.state = 0;
|
|
||||||
pressEvent.detail = mouseButton;
|
|
||||||
|
|
||||||
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, reinterpret_cast<const char *>(&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<int16_t>(x);
|
|
||||||
releaseEvent.event_y = static_cast<int16_t>(y);
|
|
||||||
releaseEvent.child = 0;
|
|
||||||
releaseEvent.state = 0;
|
|
||||||
releaseEvent.detail = mouseButton;
|
|
||||||
|
|
||||||
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char *>(&releaseEvent));
|
|
||||||
xcb_flush(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage TrayManagerProxy::getImageNonComposite() const
|
|
||||||
{
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
|
|
||||||
auto geoCookie = xcb_get_geometry(c, m_windowId);
|
|
||||||
UniqueCPointer<xcb_get_geometry_reply_t> 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<const QRgb *>(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);
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
Xembed Tray Manager Proxy - holds one embedded window
|
|
||||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPoint>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_image.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
421
util.cpp
Normal file
421
util.cpp
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <QSize>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QBitmap>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QAbstractEventDispatcher>
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <xcb/res.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_atom.h>
|
||||||
|
#include <xcb/xtest.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
#include <xcb/composite.h>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(TRAYUTIL, "org.deepin.dde.trayloader.util")
|
||||||
|
|
||||||
|
namespace tray {
|
||||||
|
void clean_xcb_image(void *data)
|
||||||
|
{
|
||||||
|
xcb_image_destroy(static_cast<xcb_image_t *>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Util* Util::instance()
|
||||||
|
{
|
||||||
|
static Util* _instance = nullptr;
|
||||||
|
if (_instance == nullptr)
|
||||||
|
_instance = new Util();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::dispatchEvents(DispatchEventsMode mode)
|
||||||
|
{
|
||||||
|
xcb_connection_t *connection = m_x11connection;
|
||||||
|
if (!connection) {
|
||||||
|
qCWarning(TRAYUTIL, "Attempting to dispatch X11 events with no connection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pollEventFunc = mode == DispatchEventsMode::Poll ? xcb_poll_for_event : xcb_poll_for_queued_event;
|
||||||
|
|
||||||
|
while (xcb_generic_event_t *event = pollEventFunc(connection)) {
|
||||||
|
qintptr result = 0;
|
||||||
|
|
||||||
|
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
|
||||||
|
dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
|
||||||
|
free(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
Util::Util()
|
||||||
|
: QObject()
|
||||||
|
{
|
||||||
|
m_x11connection = xcb_connect(nullptr, nullptr);
|
||||||
|
m_display = XOpenDisplay("");
|
||||||
|
if (!m_x11connection || !isXAvaliable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xcb_setup_t *setup = xcb_get_setup(m_x11connection);
|
||||||
|
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
|
||||||
|
xcb_screen_t* screen = iter.data;
|
||||||
|
m_rootWindow = screen->root;
|
||||||
|
|
||||||
|
xcb_ewmh_init_atoms_replies(&m_ewmh, xcb_ewmh_init_atoms(m_x11connection, &m_ewmh), nullptr);
|
||||||
|
|
||||||
|
const int fd = xcb_get_file_descriptor(m_x11connection);
|
||||||
|
QSocketNotifier * qfd = new QSocketNotifier(fd, QSocketNotifier::Read, this);
|
||||||
|
connect(qfd, &QSocketNotifier::activated, this, [this](){
|
||||||
|
dispatchEvents(DispatchEventsMode::Poll);
|
||||||
|
});
|
||||||
|
|
||||||
|
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
|
||||||
|
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, [this]() {
|
||||||
|
dispatchEvents(DispatchEventsMode::EventQueue);
|
||||||
|
});
|
||||||
|
connect(dispatcher, &QAbstractEventDispatcher::awake, this, [this]() {
|
||||||
|
dispatchEvents(DispatchEventsMode::EventQueue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Util::~Util()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Util::isXAvaliable()
|
||||||
|
{
|
||||||
|
static std::once_flag flag;
|
||||||
|
static bool avaliable = false;
|
||||||
|
|
||||||
|
std::call_once(flag, [this](){
|
||||||
|
if (!(m_x11connection && m_display)) return;
|
||||||
|
|
||||||
|
// xtest support
|
||||||
|
const xcb_query_extension_reply_t *xtest_ext_reply;
|
||||||
|
xtest_ext_reply = xcb_get_extension_data(m_x11connection, &xcb_test_id);
|
||||||
|
|
||||||
|
// xshape support
|
||||||
|
const xcb_query_extension_reply_t *xshape_ext_reply;
|
||||||
|
xshape_ext_reply = xcb_get_extension_data(m_x11connection, &xcb_shape_id);
|
||||||
|
|
||||||
|
// xewmh support
|
||||||
|
xcb_ewmh_connection_t ewmh;
|
||||||
|
xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(m_x11connection, &ewmh);
|
||||||
|
if (!ewmh_cookie) return;
|
||||||
|
|
||||||
|
xcb_ewmh_init_atoms_replies(&ewmh, ewmh_cookie, NULL);
|
||||||
|
avaliable = m_x11connection && m_display &&
|
||||||
|
(xtest_ext_reply && xtest_ext_reply->present) &&
|
||||||
|
(xshape_ext_reply && xshape_ext_reply->present) &&
|
||||||
|
(ewmh._NET_WM_STATE && ewmh._NET_WM_WINDOW_TYPE);
|
||||||
|
});
|
||||||
|
|
||||||
|
return avaliable;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_connection_t* Util::getX11Connection()
|
||||||
|
{
|
||||||
|
return m_x11connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_window_t Util::getRootWindow()
|
||||||
|
{
|
||||||
|
return m_rootWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
_XDisplay* Util::getDisplay()
|
||||||
|
{
|
||||||
|
return m_display;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_atom_t Util::getAtomByName(const QString &name)
|
||||||
|
{
|
||||||
|
xcb_atom_t ret = m_atoms.value(name, 0);
|
||||||
|
if (!ret) {
|
||||||
|
xcb_intern_atom_cookie_t cookie = xcb_intern_atom(m_x11connection, false, name.size(), name.toStdString().c_str());
|
||||||
|
QSharedPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(m_x11connection, cookie, nullptr), [=](xcb_intern_atom_reply_t* reply){
|
||||||
|
free(reply);}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (reply) {
|
||||||
|
m_atoms.insert(name, xcb_atom_t(reply->atom));
|
||||||
|
ret = reply->atom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Util::getNameByAtom(const xcb_atom_t& atom)
|
||||||
|
{
|
||||||
|
auto name = m_atoms.key(atom);
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(m_x11connection, atom);
|
||||||
|
QSharedPointer<xcb_get_atom_name_reply_t> reply(
|
||||||
|
xcb_get_atom_name_reply(m_x11connection, cookie, nullptr),
|
||||||
|
[=](xcb_get_atom_name_reply_t* reply) {free(reply);});
|
||||||
|
if (!reply) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
std::string tmp;
|
||||||
|
tmp.assign(xcb_get_atom_name_name(reply.get()), xcb_get_atom_name_name_length(reply.get()));
|
||||||
|
name = tmp.c_str();
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
m_atoms.insert(name, atom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_atom_t Util::getAtomFromDisplay(const char * name)
|
||||||
|
{
|
||||||
|
return getAtomByName(xcb_atom_name_by_screen(name, DefaultScreen(getDisplay())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::moveX11Window(const xcb_window_t& window, const uint32_t& x, const uint32_t& y)
|
||||||
|
{
|
||||||
|
const uint32_t windowMoveConfigVals[2] = {x, y};
|
||||||
|
xcb_configure_window(m_x11connection, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals);
|
||||||
|
xcb_flush(m_x11connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::setX11WindowSize(const xcb_window_t& window, const QSize& size)
|
||||||
|
{
|
||||||
|
const int windowSizeConfigVals[2] = {size.width(), size.height()};
|
||||||
|
xcb_configure_window(m_x11connection, window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, windowSizeConfigVals);
|
||||||
|
xcb_flush(m_x11connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize Util::getX11WindowSize(const xcb_window_t& window)
|
||||||
|
{
|
||||||
|
auto cookie = xcb_get_geometry(m_x11connection, window);
|
||||||
|
QSharedPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(m_x11connection, cookie, nullptr));
|
||||||
|
|
||||||
|
return clientGeom ? QSize(clientGeom->width, clientGeom->height) : QSize(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Util::getX11WindowName(const xcb_window_t& window)
|
||||||
|
{
|
||||||
|
std::string ret;
|
||||||
|
xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(&m_ewmh, window);
|
||||||
|
xcb_ewmh_get_utf8_strings_reply_t reply;
|
||||||
|
if (xcb_ewmh_get_wm_name_reply(&m_ewmh, cookie, &reply, nullptr)) {
|
||||||
|
ret.assign(reply.strings, reply.strings_len);
|
||||||
|
xcb_ewmh_get_utf8_strings_reply_wipe(&reply);
|
||||||
|
}
|
||||||
|
return ret.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::setX11WindowInputShape(const xcb_window_t& window, const QSize& size)
|
||||||
|
{
|
||||||
|
xcb_rectangle_t rectangle;
|
||||||
|
rectangle.x = 0;
|
||||||
|
rectangle.y = 0;
|
||||||
|
rectangle.width = size.width();
|
||||||
|
rectangle.height = size.height();
|
||||||
|
xcb_shape_rectangles(m_x11connection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, window, 0, 0, 1, &rectangle);
|
||||||
|
xcb_shape_mask(m_x11connection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, window, 0, 0, XCB_PIXMAP_NONE);
|
||||||
|
|
||||||
|
const uint32_t stackData[] = {size.width() > 0 && size.height() > 0 ? XCB_STACK_MODE_ABOVE : XCB_STACK_MODE_BELOW};
|
||||||
|
xcb_configure_window(m_x11connection, window, XCB_CONFIG_WINDOW_STACK_MODE, stackData);
|
||||||
|
xcb_flush(m_x11connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Util::getX11WidnowImageNonComposite(const xcb_window_t& window)
|
||||||
|
{
|
||||||
|
QSize size = getX11WindowSize(window);
|
||||||
|
xcb_image_t *image = xcb_image_get(m_x11connection, window, 0, 0, size.width(), size.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP);
|
||||||
|
|
||||||
|
QImage naiveConversion;
|
||||||
|
if (image) {
|
||||||
|
naiveConversion = QImage(image->data, image->width, image->height, QImage::Format_ARGB32);
|
||||||
|
} else {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTransparentImage(naiveConversion)) {
|
||||||
|
QImage elaborateConversion = QImage(convertFromNative(image));
|
||||||
|
if (isTransparentImage(elaborateConversion)) {
|
||||||
|
return QImage();
|
||||||
|
} else
|
||||||
|
return elaborateConversion;
|
||||||
|
} else {
|
||||||
|
return QImage(image->data, image->width, image->height, image->stride, QImage::Format_ARGB32, clean_xcb_image, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::setX11WindowOpacity(const xcb_window_t& window, const double& opacity)
|
||||||
|
{
|
||||||
|
xcb_atom_t opacityAtom = getAtomByName("_NET_WM_WINDOW_OPACITY");
|
||||||
|
quint32 value = qRound64(qBound(qreal(0), opacity, qreal(1)) * 0xffffffff);
|
||||||
|
|
||||||
|
xcb_change_property(m_x11connection,
|
||||||
|
XCB_PROP_MODE_REPLACE,
|
||||||
|
window,
|
||||||
|
opacityAtom,
|
||||||
|
XCB_ATOM_CARDINAL,
|
||||||
|
32,
|
||||||
|
1,
|
||||||
|
(uchar *)&value);
|
||||||
|
xcb_flush(m_x11connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t Util::getWindowPid(const xcb_window_t& window)
|
||||||
|
{
|
||||||
|
xcb_res_client_id_spec_t spec = { window, XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID };
|
||||||
|
xcb_res_query_client_ids_cookie_t cookie = xcb_res_query_client_ids_unchecked(m_x11connection, 1, &spec);
|
||||||
|
QSharedPointer<xcb_res_query_client_ids_reply_t> reply(xcb_res_query_client_ids_reply(m_x11connection, cookie, NULL),[](xcb_res_query_client_ids_reply_t* reply){
|
||||||
|
free(reply);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reply) {
|
||||||
|
xcb_res_client_id_value_iterator_t iter = xcb_res_query_client_ids_ids_iterator(reply.get());
|
||||||
|
for (; iter.rem; xcb_res_client_id_value_next(&iter)) {
|
||||||
|
if (iter.data->spec.mask == XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID && xcb_res_client_id_value_value_length(iter.data) == 1) {
|
||||||
|
return xcb_res_client_id_value_value(iter.data)[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// qWarning() << "failed to get pid for window: " << window;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Util::getProcExe(const pid_t& pid)
|
||||||
|
{
|
||||||
|
return QFileInfo(QString("/proc/").append(QString::number(pid).append("/exe"))).canonicalFilePath().split("/").last();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::sendXembedMessage(const xcb_window_t& window, const long& message, const long& data1, const long& data2, const long& data3)
|
||||||
|
{
|
||||||
|
xcb_client_message_event_t ev;
|
||||||
|
|
||||||
|
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||||
|
ev.window = window;
|
||||||
|
ev.format = 32;
|
||||||
|
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||||
|
ev.data.data32[1] = message;
|
||||||
|
ev.data.data32[2] = data1;
|
||||||
|
ev.data.data32[3] = data2;
|
||||||
|
ev.data.data32[4] = data3;
|
||||||
|
ev.type = getAtomByName(QStringLiteral("_XEMBED"));
|
||||||
|
xcb_send_event(m_x11connection, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Util::generateUniqueId(const QString &id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
QString newId = id + "-" + QString::number(i);
|
||||||
|
if (!m_currentIds.contains(newId)) {
|
||||||
|
m_currentIds.insert(newId);
|
||||||
|
return newId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "failed to generate unique id:" << id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Util::removeUniqueId(const QString &id) {
|
||||||
|
m_currentIds.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Util::isTransparentImage(const QImage &image)
|
||||||
|
{
|
||||||
|
int w = image.width();
|
||||||
|
int h = image.height();
|
||||||
|
|
||||||
|
if (!(qAlpha(image.pixel(w >> 1, h >> 1)) + qAlpha(image.pixel(w >> 2, h >> 2)) == 0))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
if (qAlpha(image.pixel(x, y))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Util::convertFromNative(xcb_image_t *xcbImage)
|
||||||
|
{
|
||||||
|
QImage::Format format = QImage::Format_Invalid;
|
||||||
|
|
||||||
|
switch (xcbImage->depth) {
|
||||||
|
case 1:
|
||||||
|
format = QImage::Format_MonoLSB;
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
format = QImage::Format_RGB16;
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
format = QImage::Format_RGB32;
|
||||||
|
break;
|
||||||
|
case 30: {
|
||||||
|
quint32 *pixels = reinterpret_cast<quint32 *>(xcbImage->data);
|
||||||
|
for (uint i = 0; i < (xcbImage->size / 4); i++) {
|
||||||
|
int r = (pixels[i] >> 22) & 0xff;
|
||||||
|
int g = (pixels[i] >> 12) & 0xff;
|
||||||
|
int b = (pixels[i] >> 2) & 0xff;
|
||||||
|
|
||||||
|
pixels[i] = qRgba(r, g, b, 0xff);
|
||||||
|
}
|
||||||
|
Q_FALLTHROUGH();
|
||||||
|
}
|
||||||
|
case 32:
|
||||||
|
format = QImage::Format_ARGB32_Premultiplied;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage image(xcbImage->data, xcbImage->width, xcbImage->height, xcbImage->stride, format, clean_xcb_image, xcbImage);
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format == QImage::Format_RGB32 && xcbImage->bpp == 32) {
|
||||||
|
QImage m = image.createHeuristicMask();
|
||||||
|
QPixmap p = QPixmap::fromImage(std::move(image));
|
||||||
|
p.setMask(QBitmap::fromImage(std::move(m)));
|
||||||
|
image = p.toImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.format() == QImage::Format_MonoLSB) {
|
||||||
|
image.setColorCount(2);
|
||||||
|
image.setColor(0, QColor(Qt::white).rgb());
|
||||||
|
image.setColor(1, QColor(Qt::black).rgb());
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint Util::getMousePos() const
|
||||||
|
{
|
||||||
|
QPoint pos;
|
||||||
|
xcb_query_pointer_cookie_t cookie = xcb_query_pointer(m_x11connection, m_rootWindow);
|
||||||
|
QScopedPointer<xcb_query_pointer_reply_t> reply(xcb_query_pointer_reply(m_x11connection, cookie, NULL));
|
||||||
|
if (reply) {
|
||||||
|
pos = QPoint(reply->root_x, reply->root_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
util.h
Normal file
83
util.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
#include <xcb/xcb_ewmh.h>
|
||||||
|
#include <xcb/xcb_image.h>
|
||||||
|
|
||||||
|
struct _XDisplay;
|
||||||
|
|
||||||
|
namespace tray {
|
||||||
|
#define UTIL Util::instance()
|
||||||
|
class Util : public QObject
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static Util* instance();
|
||||||
|
|
||||||
|
bool isXAvaliable();
|
||||||
|
xcb_connection_t* getX11Connection();
|
||||||
|
xcb_window_t getRootWindow();
|
||||||
|
_XDisplay* getDisplay();
|
||||||
|
|
||||||
|
xcb_atom_t getAtomByName(const QString& name);
|
||||||
|
QString getNameByAtom(const xcb_atom_t& atom);
|
||||||
|
xcb_atom_t getAtomFromDisplay(const char * name);
|
||||||
|
|
||||||
|
void moveX11Window(const xcb_window_t& window, const uint32_t& x, const uint32_t& y);
|
||||||
|
void setX11WindowSize(const xcb_window_t& window, const QSize& size);
|
||||||
|
QSize getX11WindowSize(const xcb_window_t& window);
|
||||||
|
QString getX11WindowName(const xcb_window_t& window);
|
||||||
|
void setX11WindowInputShape(const xcb_window_t& widnow, const QSize& size);
|
||||||
|
QImage getX11WidnowImageNonComposite(const xcb_window_t& window);
|
||||||
|
void setX11WindowOpacity(const xcb_window_t& window, const double& opacity);
|
||||||
|
pid_t getWindowPid(const xcb_window_t& window);
|
||||||
|
QString getProcExe(const pid_t& pid);
|
||||||
|
|
||||||
|
void sendXembedMessage(const xcb_window_t& window, const long& message, const long& data1, const long& data2, const long& data3);
|
||||||
|
|
||||||
|
QString generateUniqueId(const QString &id);
|
||||||
|
void removeUniqueId(const QString &id);
|
||||||
|
|
||||||
|
QPoint getMousePos() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Util();
|
||||||
|
~Util();
|
||||||
|
Util(const Util&) = delete;
|
||||||
|
Util& operator=(const Util&) = delete;
|
||||||
|
|
||||||
|
enum class DispatchEventsMode {
|
||||||
|
Poll,
|
||||||
|
EventQueue
|
||||||
|
};
|
||||||
|
void dispatchEvents(DispatchEventsMode mode);
|
||||||
|
|
||||||
|
bool isTransparentImage(const QImage &image);
|
||||||
|
|
||||||
|
QImage convertFromNative(xcb_image_t* image);
|
||||||
|
|
||||||
|
private:
|
||||||
|
xcb_ewmh_connection_t m_ewmh;
|
||||||
|
QHash<QString, xcb_atom_t> m_atoms;
|
||||||
|
|
||||||
|
xcb_connection_t* m_x11connection;
|
||||||
|
xcb_window_t m_rootWindow;
|
||||||
|
_XDisplay *m_display;
|
||||||
|
|
||||||
|
QSet<QString> m_currentIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
122
xcbutils.h
122
xcbutils.h
@@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
SPDX-FileCopyrightText: 2012, 2013 Martin Graesslin <mgraesslin@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2015 David Edmudson <davidedmundson@kde.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <xcb/composite.h>
|
|
||||||
#include <xcb/damage.h>
|
|
||||||
#include <xcb/randr.h>
|
|
||||||
#include <xcb/shm.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_atom.h>
|
|
||||||
#include <xcb/xcb_event.h>
|
|
||||||
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QList>
|
|
||||||
|
|
||||||
#include "c_ptr.h"
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
|
|
||||||
/** XEMBED messages */
|
|
||||||
#define XEMBED_EMBEDDED_NOTIFY 0
|
|
||||||
#define XEMBED_WINDOW_ACTIVATE 1
|
|
||||||
#define XEMBED_WINDOW_DEACTIVATE 2
|
|
||||||
#define XEMBED_REQUEST_FOCUS 3
|
|
||||||
#define XEMBED_FOCUS_IN 4
|
|
||||||
#define XEMBED_FOCUS_OUT 5
|
|
||||||
#define XEMBED_FOCUS_NEXT 6
|
|
||||||
#define XEMBED_FOCUS_PREV 7
|
|
||||||
|
|
||||||
namespace Xcb
|
|
||||||
{
|
|
||||||
typedef xcb_window_t WindowId;
|
|
||||||
|
|
||||||
class Atom
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit Atom(const QByteArray &name,
|
|
||||||
bool onlyIfExists = false,
|
|
||||||
xcb_connection_t *c = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection())
|
|
||||||
: m_connection(c)
|
|
||||||
, m_retrieved(false)
|
|
||||||
, m_cookie(xcb_intern_atom_unchecked(m_connection, onlyIfExists, name.length(), name.constData()))
|
|
||||||
, m_atom(XCB_ATOM_NONE)
|
|
||||||
, m_name(name)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
Atom() = delete;
|
|
||||||
Atom(const Atom &) = delete;
|
|
||||||
|
|
||||||
~Atom()
|
|
||||||
{
|
|
||||||
if (!m_retrieved && m_cookie.sequence) {
|
|
||||||
xcb_discard_reply(m_connection, m_cookie.sequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator xcb_atom_t() const
|
|
||||||
{
|
|
||||||
(const_cast<Atom *>(this))->getReply();
|
|
||||||
return m_atom;
|
|
||||||
}
|
|
||||||
bool isValid()
|
|
||||||
{
|
|
||||||
getReply();
|
|
||||||
return m_atom != XCB_ATOM_NONE;
|
|
||||||
}
|
|
||||||
bool isValid() const
|
|
||||||
{
|
|
||||||
(const_cast<Atom *>(this))->getReply();
|
|
||||||
return m_atom != XCB_ATOM_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const QByteArray &name() const
|
|
||||||
{
|
|
||||||
return m_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void getReply()
|
|
||||||
{
|
|
||||||
if (m_retrieved || !m_cookie.sequence) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(m_connection, m_cookie, nullptr));
|
|
||||||
if (reply) {
|
|
||||||
m_atom = reply->atom;
|
|
||||||
}
|
|
||||||
m_retrieved = true;
|
|
||||||
}
|
|
||||||
xcb_connection_t *m_connection;
|
|
||||||
bool m_retrieved;
|
|
||||||
xcb_intern_atom_cookie_t m_cookie;
|
|
||||||
xcb_atom_t m_atom;
|
|
||||||
QByteArray m_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Atoms
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Atoms()
|
|
||||||
: xembedAtom("_XEMBED")
|
|
||||||
, selectionAtom(xcb_atom_name_by_screen("_NET_SYSTEM_TRAY", DefaultScreen(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display())))
|
|
||||||
, opcodeAtom("_NET_SYSTEM_TRAY_OPCODE")
|
|
||||||
, messageData("_NET_SYSTEM_TRAY_MESSAGE_DATA")
|
|
||||||
, visualAtom("_NET_SYSTEM_TRAY_VISUAL")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Atom xembedAtom;
|
|
||||||
Atom selectionAtom;
|
|
||||||
Atom opcodeAtom;
|
|
||||||
Atom messageData;
|
|
||||||
Atom visualAtom;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Atoms *atoms;
|
|
||||||
|
|
||||||
} // namespace Xcb
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/* Wrap XLIB code in a new file as it defines keywords that conflict with Qt
|
|
||||||
|
|
||||||
SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xtestsender.h"
|
|
||||||
#include <X11/extensions/XTest.h>
|
|
||||||
|
|
||||||
void sendXTestPressed(Display *display, int button)
|
|
||||||
{
|
|
||||||
XTestFakeButtonEvent(display, button, true, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendXTestReleased(Display *display, int button)
|
|
||||||
{
|
|
||||||
XTestFakeButtonEvent(display, button, false, 0);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/* Wrap XLIB code in a new file as it defines keywords that conflict with Qt
|
|
||||||
|
|
||||||
SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
typedef struct _XDisplay Display;
|
|
||||||
|
|
||||||
void sendXTestPressed(Display *display, int button);
|
|
||||||
void sendXTestReleased(Display *display, int button);
|
|
||||||
Reference in New Issue
Block a user