Skip to content

Commit 1338d62

Browse files
committed
docs: 更新重构文档
1 parent 40490f5 commit 1338d62

File tree

1 file changed

+63
-193
lines changed

1 file changed

+63
-193
lines changed

rust_tray/plan.md

Lines changed: 63 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,193 +1,63 @@
1-
# 系统托盘替换方案
2-
3-
## 目标
4-
将现有的基于 C 库 `tray` 的系统托盘实现替换为 Rust 的 `tray-icon` 库,以提高可维护性并减少跨平台适配代码。
5-
6-
## 总体方案
7-
1. 在项目根目录下创建 Rust 子目录 `rust_tray`,编写静态库实现与原 `tray` 库完全兼容的 C API(`tray_init``tray_update``tray_loop``tray_exit`)。
8-
2. 使用 `bindgen` 从原 `third-party/tray/src/tray.h` 生成 Rust 绑定,保证结构体布局一致。
9-
3. 修改 CMake 构建系统,移除对原 `tray` 库的编译,改为构建并链接 Rust 静态库。
10-
4. 保留 `third-party/tray` 子模块中的头文件,确保 C++ 代码 `#include "tray/src/tray.h"` 仍然有效。
11-
5. 不修改 `src/system_tray.cpp` 的业务逻辑,仅替换底层库的实现。
12-
13-
## 详细步骤
14-
15-
### 1. 创建 Rust 项目
16-
`rust_tray/` 下初始化 Cargo 库:
17-
18-
```
19-
rust_tray/
20-
├── Cargo.toml
21-
├── build.rs
22-
└── src/
23-
└── lib.rs
24-
```
25-
26-
**Cargo.toml 内容:**
27-
28-
```toml
29-
[package]
30-
name = "sunshine_tray"
31-
version = "0.1.0"
32-
edition = "2021"
33-
34-
[lib]
35-
name = "tray"
36-
crate-type = ["staticlib"]
37-
38-
[dependencies]
39-
tray-icon = { version = "0.10", default-features = false, features = ["tray"] }
40-
anyhow = "1.0"
41-
lazy_static = "1.4"
42-
libc = "0.2"
43-
log = "0.4"
44-
45-
[build-dependencies]
46-
bindgen = "0.69"
47-
```
48-
49-
### 2. 生成 FFI 绑定(build.rs)
50-
利用 `bindgen` 解析原头文件,生成与 C 完全一致的 Rust 结构体定义。
51-
52-
```rust
53-
// build.rs
54-
use std::env;
55-
use std::path::PathBuf;
56-
57-
fn main() {
58-
let bindings = bindgen::Builder::default()
59-
.header("third-party/tray/src/tray.h")
60-
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
61-
.generate()
62-
.expect("Unable to generate bindings");
63-
64-
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
65-
bindings
66-
.write_to_file(out_path.join("bindings.rs"))
67-
.expect("Couldn't write bindings");
68-
}
69-
```
70-
71-
### 3. 实现 C API(lib.rs)
72-
73-
主要任务:
74-
75-
- 全局状态管理(`OnceLock<Mutex<Option<TrayState>>>`),存储 `TrayIcon` 实例及菜单项到 C 菜单的映射。
76-
- `tray_init`:根据传入的 `tray` 结构体创建托盘图标和菜单,注册回调(调用 C 回调)。
77-
- `tray_update`:更新图标、工具提示、菜单文本、勾选状态等;若设置了 `notification_*` 字段,显示通知。
78-
- `tray_loop`:启动平台事件循环(例如 GTK 的 `main` 或 Windows 消息循环),阻塞直到 `tray_exit` 被调用。
79-
- `tray_exit`:退出事件循环,清理资源。
80-
81-
关键代码框架:
82-
83-
```rust
84-
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
85-
86-
use std::ffi::{CStr, CString};
87-
use std::os::raw::{c_char, c_int, c_void};
88-
use std::sync::{Mutex, OnceLock};
89-
use tray_icon::{TrayIcon, TrayIconBuilder, Menu, MenuItem, MenuId, TrayIconEvent};
90-
use anyhow::Result;
91-
92-
struct TrayState {
93-
icon: TrayIcon,
94-
menu_map: Vec<(MenuId, *const tray_menu)>, // 用于回调查找
95-
// 事件循环句柄(例如 gtk::Application 或 winit 事件循环)
96-
event_loop: Option<...>,
97-
}
98-
99-
static TRAY_STATE: OnceLock<Mutex<Option<TrayState>>> = OnceLock::new();
100-
101-
#[no_mangle]
102-
pub extern "C" fn tray_init(t: *mut tray) -> c_int {
103-
// 错误处理返回 -1,成功 0
104-
match unsafe { do_init(t) } {
105-
Ok(_) => 0,
106-
Err(_) => -1,
107-
}
108-
}
109-
110-
unsafe fn do_init(t: *mut tray) -> Result<()> {
111-
// 构建菜单(递归)
112-
let (menu, menu_map) = build_menu((*t).menu)?;
113-
114-
// 加载图标
115-
let icon = load_icon(CStr::from_ptr((*t).icon).to_str()?)?;
116-
117-
let builder = TrayIconBuilder::new()
118-
.with_icon(icon)
119-
.with_tooltip(CStr::from_ptr((*t).tooltip).to_str()?)
120-
.with_menu(Box::new(menu));
121-
122-
let tray_icon = builder.build()?;
123-
124-
// 存储状态
125-
let state = TrayState {
126-
icon: tray_icon,
127-
menu_map,
128-
event_loop: None,
129-
};
130-
TRAY_STATE.get_or_init(|| Mutex::new(None))
131-
.lock()
132-
.unwrap()
133-
.replace(state);
134-
135-
Ok(())
136-
}
137-
138-
// 其他函数类似实现
139-
```
140-
141-
菜单构建时需递归处理子菜单,并为每个 `MenuItem` 设置回调:当用户点击时,根据 `MenuId``menu_map` 中找到对应的 C `tray_menu` 指针,调用其 `cb` 字段(若存在)。
142-
143-
### 4. 修改 CMake 构建
144-
145-
编辑 `cmake/targets/common.cmake`,注释或删除对原 `tray` 库的引用,替换为自定义命令构建 Rust 库。
146-
147-
```cmake
148-
# 禁用原 tray 库
149-
# add_subdirectory(third-party/tray)
150-
151-
# 添加 Rust 托盘库构建
152-
set(RUST_TRAY_SOURCE_DIR "${CMAKE_SOURCE_DIR}/rust_tray")
153-
set(RUST_TARGET_DIR "${CMAKE_BINARY_DIR}/rust_tray")
154-
set(RUST_OUTPUT_DEBUG "${RUST_TARGET_DIR}/debug/libtray.a")
155-
# Release 构建可根据需要选择
156-
add_custom_command(
157-
OUTPUT ${RUST_OUTPUT_DEBUG}
158-
COMMAND ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${RUST_TARGET_DIR} cargo build --manifest-path ${RUST_TRAY_SOURCE_DIR}/Cargo.toml
159-
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
160-
COMMENT "Building Rust Tray library (debug)"
161-
VERBATIM
162-
)
163-
add_custom_target(rust_tray ALL DEPENDS ${RUST_OUTPUT_DEBUG})
164-
165-
# 链接静态库
166-
target_link_libraries(sunshine PRIVATE ${RUST_OUTPUT_DEBUG})
167-
target_include_directories(sunshine PRIVATE third-party/tray/src)
168-
```
169-
170-
注意:根据实际构建类型(Debug/Release)调整 cargo 参数和输出路径,也可同时构建 Release 版本并通过 CMake 变量选择。
171-
172-
### 5. 验证与测试
173-
174-
编译 Sunshine,确保无链接错误。运行后验证:
175-
- 系统托盘图标显示正常。
176-
- 菜单点击功能正常(打开 UI、开关显示器、导入导出配置、语言切换等)。
177-
- 通知正常显示(例如开始/暂停串流、配对请求)。
178-
- 图标切换(播放、暂停、锁定)正常。
179-
180-
## 可能的问题及应对
181-
182-
- **回调中 C 字符串的生命周期**`tray_menu.text` 在语言切换时会指向新的 `std::string` 内部数据,而 Rust 在 `tray_update` 时会重新拷贝字符串,因此安全。
183-
- **事件循环集成**:不同平台事件循环实现方式不同,需确保 `tray_loop` 阻塞且能正确响应退出。可参考 `tray-icon` 示例中的事件循环代码(如 winit、gtk)。
184-
- **Windows 系统权限问题**:原 `system_tray.cpp` 中的线程 DACL 修改和等待 Shell 的代码保留,不影响 Rust 库。
185-
- **跨平台图标格式**:原代码已通过宏区分各平台图标路径/名称,直接传递给 `tray-icon` 即可,该库会自动处理。
186-
187-
## 后续工作
188-
189-
- 移除 `third-party/tray` 中除头文件外的源文件(可选,但保留子模块可方便获取头文件)。
190-
- 完善 Rust 实现中的错误处理和日志记录。
191-
- 如有必要,在 CI 中增加 Rust 工具链安装步骤。
192-
193-
本方案通过最小侵入式修改实现了核心功能替换,可显著降低维护成本并提高跨平台稳定性。
1+
# 系统托盘替换方案(已更新:大部分逻辑迁移至 Rust 库)
2+
3+
要点结论:
4+
- 大部分托盘逻辑、i18n 与菜单处理已迁移到 Rust 库,主实现见 [`rust_tray/src/lib.rs`](rust_tray/src/lib.rs:1)
5+
- 对外 C API 以扩展接口为主:请使用 [`tray_init_ex``tray_loop``tray_exit`](rust_tray/include/rust_tray.h:61);旧 `tray_init` 为遗留且不推荐使用。
6+
- C++ 端使用薄包装器 [`src/system_tray_rust.cpp`](src/system_tray_rust.cpp:1) 与 Rust 库交互,CMake 已改为始终链接 Rust 实现。
7+
8+
关键文件(快速索引):
9+
- Rust 实现:[`rust_tray/src/lib.rs`](rust_tray/src/lib.rs:1)
10+
- 国际化:[`rust_tray/src/i18n.rs`](rust_tray/src/i18n.rs:1)
11+
- 菜单/动作:[`rust_tray/src/actions.rs`](rust_tray/src/actions.rs:1)
12+
- C 头(导出 API):[`rust_tray/include/rust_tray.h`](rust_tray/include/rust_tray.h:1)
13+
- C++ 包装器:[`src/system_tray_rust.cpp`](src/system_tray_rust.cpp:1)
14+
- CMake 目标:[`cmake/targets/rust_tray.cmake`](cmake/targets/rust_tray.cmake:1)
15+
16+
架构要点:
17+
1. Rust 负责:菜单结构、i18n、事件循环、图标/通知、动作映射(MenuAction -> TrayAction)。
18+
2. C++ 负责:应用内响应(打开 UI、重启、退出等)和平台特殊处理(如 Windows 特权/进程管理)。
19+
3. 边界:Rust 通过 C API 导出简单函数;C++ 通过回调接收用户操作事件。
20+
21+
构建与集成:
22+
- CMake 现在包含并构建 `rust_tray`,使用 `cargo build` 生成静态库并链接到主程序(见 [`cmake/compile_definitions/*`](cmake/compile_definitions/common.cmake:1) 的改动)。
23+
- 头文件为 [`rust_tray/include/rust_tray.h`](rust_tray/include/rust_tray.h:1),C++ 仅需包含该头并注册回调。
24+
- CI:需保证 Rust toolchain 可用;建议在 CI 中添加 Rust 安装步骤。
25+
26+
运行时与 API 变化:
27+
- 初始化:推荐使用 `tray_init_ex(icon_normal, icon_playing, icon_pausing, icon_locked, tooltip, locale, callback)`
28+
- 事件循环:使用 `tray_loop(blocking)` 驱动;返回 -1 表示要求退出。可在单线程或分线程中调用(包装器提供线程化入口)。
29+
- 运行时更新:`tray_set_icon``tray_set_tooltip``tray_set_vdd_checked``tray_set_vdd_enabled``tray_set_locale``tray_show_notification`
30+
- 兼容层:实现了 `tray_update`(部分支持);但 `tray_init` 已被降级(返回错误并打印警告)。
31+
32+
i18n 与菜单:
33+
- i18n 数据与逻辑在 Rust 层管理,参见 [`rust_tray/src/i18n.rs`](rust_tray/src/i18n.rs:1)
34+
- 语言切换由 Rust 处理并原子更新菜单文本,必要时会重设 TrayIcon 的菜单以确保生效(Windows 行为)。
35+
36+
图标与通知:
37+
- 图标加载:Windows 优先使用 .ico(多分辨率),Linux 支持图标名称或文件路径,macOS 使用文件路径。
38+
- 通知:当前为占位实现(日志输出),需要按平台补全真实通知接口(待办)。
39+
40+
测试清单(必验):
41+
- 编译通过并链接 Rust 静态库。
42+
- 托盘图标在 Windows / Linux / macOS 显示正确。
43+
- 菜单项触发后,C++ 回调收到匹配的 `TrayAction`(见头文件枚举)。
44+
- 语言切换后菜单文本更新并在 UI 上可见。
45+
- 图标切换与 tooltip 更新正常。
46+
- 通知调用至少不会崩溃(后续完善行为)。
47+
48+
已知限制与后续工作:
49+
- 完成平台通知实现(Rust 层需要具体实现)。
50+
- 若需更细粒度的日志或错误上报,考虑在 Rust 层引入更丰富的日志接口并暴露给 C++。
51+
- 可选:清理 `third-party/tray` 中多余源文件,仅保留头文件以减小仓库体积。
52+
- 在 CI 中加入交叉编译与多平台验证。
53+
54+
迁移结论:
55+
本次提交把「菜单、i18n、事件循环、图标管理、部分文件对话(导入/导出)」这些横跨平台且逻辑密集的功能迁移到 Rust,提高了可维护性与一致性。C++ 侧保留平台特性与应用逻辑,双方通过稳定的 C API 协作。
56+
57+
参考实现与调试入口:
58+
- 查看实现:[`rust_tray/src/lib.rs`](rust_tray/src/lib.rs:1)
59+
- 头文件:[`rust_tray/include/rust_tray.h`](rust_tray/include/rust_tray.h:1)
60+
- C++ 包装示例:[`src/system_tray_rust.cpp`](src/system_tray_rust.cpp:1)
61+
- i18n 数据:[`rust_tray/src/i18n.rs`](rust_tray/src/i18n.rs:1)
62+
63+
完成。

0 commit comments

Comments
 (0)