Skip to content

feat: rust托盘图标#338

Open
ShadowLemoon wants to merge 38 commits intoAlkaidLab:masterfrom
ShadowLemoon:feat/rust-tray-icon
Open

feat: rust托盘图标#338
ShadowLemoon wants to merge 38 commits intoAlkaidLab:masterfrom
ShadowLemoon:feat/rust-tray-icon

Conversation

@ShadowLemoon
Copy link
Copy Markdown
Collaborator

@ShadowLemoon ShadowLemoon commented Dec 2, 2025

Summary by CodeRabbit

发行说明

  • 新功能

    • 系统托盘支持深色/浅色主题模式切换
    • 增强的通知系统,支持自定义标题与内容
    • 改进了VDD菜单的交互响应
  • 改进

    • 优化了系统托盘在Windows平台上的稳定性和兼容性
    • 提升了托盘菜单的国际化支持(英文、中文、日文)

Copilot AI review requested due to automatic review settings December 3, 2025 07:27
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a new system tray icon feature using Rust, replacing the C-based tray implementation. The Rust library handles menu structure, internationalization (Chinese, English, Japanese), event loops, and icon management, while C++ provides a thin wrapper for application-specific callbacks and platform-specific operations. The implementation uses the tray-icon and muda Rust crates with a C FFI boundary for cross-language integration.

Key changes:

  • Migrates tray icon logic from C to Rust with multi-language support
  • Introduces new C FFI API (tray_init_ex, tray_loop, tray_exit) for tray lifecycle management
  • Integrates Rust build into CMake workflow with platform-specific target detection

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/system_tray_rust.cpp C++ wrapper providing callbacks for Rust tray events and platform-specific handlers (Windows process termination)
rust_tray/src/lib.rs Core Rust tray implementation with menu building, event handling, and FFI exports
rust_tray/src/i18n.rs Internationalization module with translation tables for Chinese, English, and Japanese
rust_tray/src/actions.rs Menu action definitions and URL handling for browser integration
rust_tray/include/rust_tray.h C API header defining FFI interface between Rust and C++
rust_tray/build.rs Cargo build script for platform-specific linker configuration
rust_tray/Cargo.toml Rust package manifest with dependencies and build configuration
rust_tray/README.md Documentation for Rust tray library (contains outdated information)
rust_tray/plan.md Implementation plan and architecture documentation (in Chinese)
cmake/targets/rust_tray.cmake CMake configuration for building and linking Rust static library
cmake/compile_definitions/*.cmake Platform-specific CMake changes to integrate Rust tray library
.github/workflows/main.yml CI workflow update to add MinGW Rust target for Windows builds

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

// Get locale from config
std::string locale = "zh"; // Default to Chinese
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default locale is hardcoded to Chinese ("zh"), but the default locale in the Rust i18n module is English. This inconsistency could cause unexpected behavior. Consider defaulting to English here as well, or make the default configurable.

Suggested change
std::string locale = "zh"; // Default to Chinese
std::string locale = "en"; // Default to English

Copilot uses AI. Check for mistakes.
Comment on lines +279 to +304
void update_tray_playing(std::string app_name) {
if (!tray_initialized) return;

tray_set_icon(TRAY_ICON_TYPE_PLAYING);

std::string tooltip = "Sunshine - Playing: " + app_name;
tray_set_tooltip(tooltip.c_str());
}

void update_tray_pausing(std::string app_name) {
if (!tray_initialized) return;

tray_set_icon(TRAY_ICON_TYPE_PAUSING);

std::string tooltip = "Sunshine - Paused: " + app_name;
tray_set_tooltip(tooltip.c_str());
}

void update_tray_stopped(std::string app_name) {
if (!tray_initialized) return;

tray_set_icon(TRAY_ICON_TYPE_NORMAL);

std::string tooltip = "Sunshine "s + PROJECT_VER;
tray_set_tooltip(tooltip.c_str());
}
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions update_tray_playing, update_tray_pausing, and update_tray_stopped take std::string by value, which causes unnecessary string copies. Consider using const std::string& instead for better performance, especially since these functions may be called frequently during streaming.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +182
BOOST_LOG(info) << "Successfully terminated sunshine-gui.exe"sv;
}
CloseHandle(process_handle);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling for Windows API calls is incomplete. If OpenProcess fails (returns NULL), the code silently continues without logging the error. Similarly, TerminateProcess failure is not logged. Consider adding error logging to aid in debugging permission or access issues.

Suggested change
BOOST_LOG(info) << "Successfully terminated sunshine-gui.exe"sv;
}
CloseHandle(process_handle);
BOOST_LOG(info) << "Successfully terminated sunshine-gui.exe"sv;
} else {
DWORD err = GetLastError();
BOOST_LOG(error) << "Failed to terminate sunshine-gui.exe (PID: " << pe32.th32ProcessID << "), error: " << err;
}
CloseHandle(process_handle);
} else {
DWORD err = GetLastError();
BOOST_LOG(error) << "Failed to open sunshine-gui.exe (PID: " << pe32.th32ProcessID << ") for termination, error: " << err;

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +188
void terminate_gui_processes() {
#ifdef _WIN32
BOOST_LOG(info) << "Terminating sunshine-gui.exe processes..."sv;

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (Process32FirstW(snapshot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, L"sunshine-gui.exe") == 0) {
BOOST_LOG(info) << "Found sunshine-gui.exe (PID: " << pe32.th32ProcessID << "), terminating..."sv;
HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
if (process_handle != NULL) {
if (TerminateProcess(process_handle, 0)) {
BOOST_LOG(info) << "Successfully terminated sunshine-gui.exe"sv;
}
CloseHandle(process_handle);
}
}
} while (Process32NextW(snapshot, &pe32));
}
CloseHandle(snapshot);
}
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminate_gui_processes function terminates all sunshine-gui.exe processes without checking if they belong to the current user session or if they're associated with this specific Sunshine instance. In a multi-user environment or when multiple Sunshine instances are running, this could terminate GUI processes belonging to other users or instances, which is a security and usability issue.

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +83
std::thread([]() {
std::this_thread::sleep_for(10s);
tray_set_vdd_enabled(1);
}).detach();
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detached thread created here is not tracked or joined, which could lead to a race condition during shutdown. If the application exits before the 10-second sleep completes, the thread may attempt to call tray_set_vdd_enabled on an already-destroyed tray. Consider using a cancellable timer or tracking the thread to ensure proper cleanup.

Copilot uses AI. Check for mistakes.
- 将菜单事件处理拆分为 identify_menu_action 和 execute_action 两步
- 先释放 mutex 锁再执行实际操作,避免切换语言、重启、退出时死锁
Copilot AI review requested due to automatic review settings January 24, 2026 15:47
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +5
//! Windows Toast Notification module
//!
//! Provides native Windows toast notifications using WinRT.

use winrt_notification::{Duration, Sound, Toast};
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to dialogs.rs, this notification module uses Windows-specific APIs (winrt_notification) but lacks explicit platform guards. While lib.rs has the guard, add #![cfg(target_os = "windows")] at the module level for clarity and to prevent misuse.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +61
├── Cargo.toml # Rust package manifest
├── build.rs # Build script (platform-specific setup)
├── src/
│ ├── lib.rs # Main library implementation
│ └── ffi.rs # FFI type definitions
└── README.md # This file
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The architecture documentation shows src/ffi.rs which doesn't exist in the actual implementation. The actual structure includes i18n.rs, actions.rs, dialogs.rs, menu.rs, menu_items.rs, and notification.rs. Update the architecture diagram to reflect the actual file structure.

Suggested change
├── Cargo.toml # Rust package manifest
├── build.rs # Build script (platform-specific setup)
├── src/
│ ├── lib.rs # Main library implementation
│ └── ffi.rs # FFI type definitions
└── README.md # This file
├── Cargo.toml # Rust package manifest
├── build.rs # Build script (platform-specific setup)
├── src/
│ ├── lib.rs # Main library implementation and C FFI
│ ├── i18n.rs # Internationalization and localized strings
│ ├── actions.rs # Actions and callbacks triggered from the tray
│ ├── dialogs.rs # Platform-specific dialog helpers
│ ├── menu.rs # Tray menu construction and management
│ ├── menu_items.rs # Individual tray menu item definitions
│ └── notification.rs # User notifications from the tray
└── README.md # This file

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +68
- No changes are required to `src/system_tray.cpp`
- The original `third-party/tray/src/tray.h` header is still used for the C++ code
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README claims "No changes are required to src/system_tray.cpp" but this is incorrect - the PR actually replaces system_tray.cpp with system_tray_rust.cpp (as shown in common.cmake line 120). This statement is misleading and should be removed or updated to reflect the actual changes made.

Suggested change
- No changes are required to `src/system_tray.cpp`
- The original `third-party/tray/src/tray.h` header is still used for the C++ code
- When `SUNSHINE_USE_RUST_TRAY=ON` is enabled, the Rust-based implementation is used in place of the original `src/system_tray.cpp` implementation via CMake
- The original `third-party/tray/src/tray.h` header is still used as the public API header for the C++ code

Copilot uses AI. Check for mistakes.
version = "0.1.0"
edition = "2021"
build = "build.rs"
description = "Windows-only system tray implementation for Sunshine"
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package description claims "Windows-only system tray implementation" but the CMake configuration (rust_tray.cmake) includes platform-specific support for macOS (lines 102-108) and Linux (lines 110-118). Additionally, the README states "Cross-platform support (Windows, Linux, macOS)". Either the description is outdated, or the non-Windows support is not actually functional (since lib.rs has #[cfg(target_os = "windows")]). Clarify the actual platform support or update the description and documentation to be consistent.

Suggested change
description = "Windows-only system tray implementation for Sunshine"
description = "System tray implementation for Sunshine (currently targeting Windows)"

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +49
// Safety: TrayState is only accessed from the main thread
unsafe impl Send for TrayState {}
unsafe impl Sync for TrayState {}
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unsafe Send and Sync implementations for TrayState (lines 48-49) are justified with a comment that "TrayState is only accessed from the main thread". However, the TrayState is wrapped in a Mutex (line 52) which suggests it might be accessed from multiple threads. If it's truly only accessed from the main thread, the Mutex is unnecessary. If it can be accessed from multiple threads, the unsafe Send/Sync impls need more careful justification, especially since TrayIcon and Menu from the tray-icon and muda crates may not be thread-safe. Review whether the Mutex is needed and whether the unsafe impls are truly safe.

Suggested change
// Safety: TrayState is only accessed from the main thread
unsafe impl Send for TrayState {}
unsafe impl Sync for TrayState {}
// Safety: TrayState is created and used only on the main thread.
// It is never moved out of the global Mutex/OnceCell, and all access
// must remain confined to the main thread. The Send bound is required
// only so that `Mutex<Option<TrayState>>` can be used in a global.
unsafe impl Send for TrayState {}

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +104
std::thread([]() {
std::this_thread::sleep_for(10s);
s_vdd_in_cooldown = false;
update_vdd_menu_state();
}).detach();
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detached thread used for VDD cooldown creates a fire-and-forget timer that cannot be cancelled or properly cleaned up. If end_tray() is called while a cooldown is in progress, the detached thread will continue to run and attempt to call update_vdd_menu_state() after the tray may have been destroyed. Consider using a cancellable timer mechanism or tracking detached threads to ensure proper cleanup on shutdown.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
//! Dialog utilities module (Windows only)
//!
//! 提供系统对话框功能。

use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module uses Windows-specific APIs (std::os::windows::ffi::OsStrExt and windows_sys) but lacks a #[cfg(target_os = "windows")] attribute. While the parent lib.rs has #[cfg(target_os = "windows")] on line 14, it's a best practice to also annotate platform-specific modules to prevent accidental usage on other platforms. Add #![cfg(target_os = "windows")] at the top of this module.

Copilot uses AI. Check for mistakes.
${RUST_TRAY_SOURCE_DIR}/build.rs
${RUST_TRAY_SOURCE_DIR}/src/lib.rs
${RUST_TRAY_SOURCE_DIR}/src/i18n.rs
${RUST_TRAY_SOURCE_DIR}/src/actions.rs
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CMake DEPENDS list for the Rust build doesn't include all Rust source files. It only lists lib.rs, i18n.rs, and actions.rs, but is missing dialogs.rs, menu.rs, menu_items.rs, and notification.rs. This means changes to these files won't trigger a rebuild. Add all source files to the DEPENDS list to ensure proper incremental builds.

Suggested change
${RUST_TRAY_SOURCE_DIR}/src/actions.rs
${RUST_TRAY_SOURCE_DIR}/src/actions.rs
${RUST_TRAY_SOURCE_DIR}/src/dialogs.rs
${RUST_TRAY_SOURCE_DIR}/src/menu.rs
${RUST_TRAY_SOURCE_DIR}/src/menu_items.rs
${RUST_TRAY_SOURCE_DIR}/src/notification.rs

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +270
assert_eq!(get_string(StringKey::OpenSunshine), "打开 Sunshine");

set_locale(Locale::Japanese);
assert_eq!(get_string(StringKey::OpenSunshine), "Sunshineを開く");
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test expects "打开 Sunshine" for Chinese translation, but the actual translation on line 129 is "打开基地面板" (which means "Open Base Panel"). Similarly, the test expects "Sunshineを開く" for Japanese but the actual translation on line 168 is "GUIを開く" (which means "Open GUI"). Update the tests to match the actual translations or update the translations to match the tests.

Suggested change
assert_eq!(get_string(StringKey::OpenSunshine), "打开 Sunshine");
set_locale(Locale::Japanese);
assert_eq!(get_string(StringKey::OpenSunshine), "Sunshineを開く");
assert_eq!(get_string(StringKey::OpenSunshine), "打开基地面板");
set_locale(Locale::Japanese);
assert_eq!(get_string(StringKey::OpenSunshine), "GUIを開く");

Copilot uses AI. Check for mistakes.
Comment on lines +262 to +270
fn test_get_string() {
set_locale(Locale::English);
assert_eq!(get_string(StringKey::OpenSunshine), "Open Sunshine");

set_locale(Locale::Chinese);
assert_eq!(get_string(StringKey::OpenSunshine), "打开 Sunshine");

set_locale(Locale::Japanese);
assert_eq!(get_string(StringKey::OpenSunshine), "Sunshineを開く");
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test expects "Open Sunshine" for English translation, but the actual translation defined on line 90 is "Open GUI". This test will fail. Either update the test expectation to match the actual translation or update the translation to match the test.

Copilot uses AI. Check for mistakes.
@qiin2333 qiin2333 force-pushed the master branch 2 times, most recently from 029ba99 to eba4145 Compare February 14, 2026 06:38
@qiin2333 qiin2333 force-pushed the master branch 2 times, most recently from e6bbe21 to d74f9ed Compare March 28, 2026 13:24
@ShadowLemoon ShadowLemoon requested a review from Yundi339 March 29, 2026 19:18
@ShadowLemoon
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

概述

本变更将系统托盘功能从 C 实现迁移至 Rust,新增 rust_tray 库以跨平台支持 Windows/macOS/Linux 托盘操作。CMake 配置更新以构建并链接 Rust 静态库,同时新增 C++ 包装层 (system_tray_rust.cpp) 将 Rust FFI 接口适配至现有 C++ 应用程序。

变更

组件分类 / 文件 变更摘要
CI/构建配置
.github/workflows/main.yml, cmake/compile_definitions/common.cmake, cmake/compile_definitions/windows.cmake, cmake/targets/rust_tray.cmake, rust_tray/.cargo/config.toml
添加 Windows GNU 目标支持;更新 CMake 编译列表以使用 system_tray_rust.cpp;Windows 构建从原生 C tray 库切换到 Rust 实现;新增 Rust 静态库构建脚本,自动检测 Cargo、计算目标三元组、配置平台特定链接库(Windows/macOS/Linux)。
Rust 库基础设施
rust_tray/.gitignore, rust_tray/Cargo.toml, rust_tray/build.rs, rust_tray/README.md, rust_tray/plan.md
新增 Rust crate 清单与构建脚本,声明依赖(tray-icon, muda, winit, windows-sys 等);文档说明迁移范围、API 调用方式、i18n 行为、集成要点及已知限制。
Rust 核心实现
rust_tray/src/lib.rs, rust_tray/src/i18n.rs, rust_tray/src/actions.rs, rust_tray/src/menu.rs, rust_tray/src/menu_items.rs, rust_tray/src/notification.rs, rust_tray/src/dialogs.rs
实现 FFI 导出函数(初始化、事件循环、退出、更新菜单/图标/通知),国际化(语言切换与字符串翻译),菜单构造与状态管理,动作分发回调,Windows 通知与确认对话框。
公共 API 头文件
rust_tray/include/rust_tray.h
定义 C API 头文件,声明托盘初始化、事件循环、菜单更新、通知及夜间模式配置等公开函数与类型。
C++ 集成层
src/system_tray.h, src/system_tray_rust.cpp
更新头文件,移除旧回调声明,新增 VDD 菜单相关 API;实现 C++ 包装层以初始化 Rust 托盘、路由动作回调至 C++ 业务逻辑(启动 UI、VDD 创建/关闭、重启/退出、外部链接、显示配置重置)、管理冷却时间与菜单状态、Windows GUI 进程清理。

时序图

sequenceDiagram
    participant CppApp as C++ 应用
    participant RustTray as Rust 托盘库
    participant WinAPI as Windows API
    participant MenuSys as 菜单系统

    CppApp->>RustTray: tray_init_ex(图标, 本地化, 回调)
    activate RustTray
    RustTray->>MenuSys: 构建本地化菜单
    RustTray->>WinAPI: 创建 TrayIcon
    RustTray->>WinAPI: 注册 TrayIcon 到系统托盘
    deactivate RustTray
    
    Note over WinAPI: 用户交互...
    
    CppApp->>RustTray: tray_loop(非阻塞)
    activate RustTray
    RustTray->>WinAPI: 处理 Windows 消息
    WinAPI-->>RustTray: 菜单事件
    RustTray->>MenuSys: 识别菜单项 ID
    RustTray->>RustTray: 执行本地处理器
    RustTray-->>CppApp: 回调(动作 ID)
    deactivate RustTray
    
    activate CppApp
    CppApp->>CppApp: 路由至业务逻辑(启动 UI/VDD/重启/退出)
    CppApp->>RustTray: tray_update_vdd_menu(...)
    CppApp->>RustTray: tray_set_locale(新语言)
    deactivate CppApp
    
    activate RustTray
    RustTray->>MenuSys: 使用新语言重建菜单
    RustTray->>WinAPI: 刷新 TrayIcon
    deactivate RustTray
Loading

代码审查工作量评估

🎯 4 (复杂) | ⏱️ ~75 分钟

评估理由:本变更涉及多个维度的复杂性——(1)新增 541 行 Rust FFI 实现与多个子模块(菜单、i18n、通知),每个模块引入新的数据结构与交互;(2)跨语言集成:Rust ↔ C++ ↔ Windows API,需验证 FFI 边界的内存安全与类型正确性;(3)菜单状态管理与多线程访问(RwLock、Lazy);(4)CMake 构建逻辑新增(平台检测、Cargo 调用、目标三元组映射);(5)动作回调链路涉及字符串转换、本地化与冷却时间管理;(6)354 行 C++ 包装层涉及原子变量、全局状态与 Windows API 使用。代码异构性较高(Rust 模块多样、CMake 新逻辑、C++ 中介),需针对各部分分别理解与验证。

诗歌

🐰 Rust 的托盘闪闪发光,
跨越平台齐飞翔,
菜单、通知、事件流,
FFI 的桥接真妙哉,
国际化语言宝藏,
代码共舞,功能昌! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题"feat: rust托盘图标"清晰准确地反映了本次PR的主要变更:将系统托盘图标实现从C迁移到Rust,与提交历史和文件改动完全对应。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (5)
rust_tray/.gitignore (1)

1-2: 考虑是否应该提交 Cargo.lock

对于作为可执行程序一部分的 Rust 库,通常建议提交 Cargo.lock 以确保构建的可复现性。虽然对于纯 Rust 库通常会忽略此文件,但由于 rust_tray 是集成到 C++ 应用程序中的静态库,提交 Cargo.lock 可以确保所有开发者和 CI 使用相同的依赖版本。

🔧 建议的修改
 /target/
-/Cargo.lock
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/.gitignore` around lines 1 - 2, 当前 .gitignore 忽略了 Cargo.lock,但因为
rust_tray 是作为静态库/可执行程序的一部分被集成到 C++ 应用中,应提交 Cargo.lock 以保证依赖可复现;请在 rust_tray 的
.gitignore 中移除 Cargo.lock(或直接将 Cargo.lock 添加到仓库并提交),并在以后更新依赖后保持同步提交更新的
Cargo.lock(定位到 .gitignore 和 Cargo.lock 文件以修改/提交)。
rust_tray/src/i18n.rs (1)

237-240: 缺失翻译时考虑添加日志记录

当翻译键在所有语言中都找不到时,函数静默返回空字符串。这可能导致 UI 中出现空白文本而难以调试。建议在此处添加日志警告,便于开发阶段发现遗漏的翻译。

♻️ 建议的修改
     // Fallback to English
     if let Some(s) = TRANSLATIONS.get(&(Locale::English, key)) {
         return s;
     }

     // Return empty string if not found
+    // Consider: log::warn!("Missing translation for key: {:?}", key);
     ""
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/src/i18n.rs` around lines 237 - 240, The function in
rust_tray/src/i18n.rs that currently ends by returning an empty string (the
final `""`) should log a warning when a translation key is missing instead of
silently returning empty; update the function (e.g., the translate/t or
equivalent lookup function that contains the `return ""`) to call your logging
facility (warn! or tracing::warn!) with the missing key and context (requested
locale(s) and/or the list of available languages) before returning the empty
string so missing translations are discoverable during development.
rust_tray/src/lib.rs (1)

531-538: 配对请求通知的格式化模式与其他类型不同,但逻辑正确

第 3 种通知类型(配对请求)的格式化模式与前三种相反:标题使用格式化字符串,正文使用普通字符串。这是因为配对请求需要在标题中显示设备名称。代码逻辑正确。

默认分支 _ => return 静默返回,建议添加日志记录无效的通知类型以便调试。

♻️ 建议添加日志
         3 => {
             // Pairing request
             let title = i18n::get_string_fmt(i18n::StringKey::IncomingPairingRequest, &app_name_str);
             let text = i18n::get_string(i18n::StringKey::ClickToCompletePairing);
             (title, text.to_string(), notification::NotificationIcon::Normal)
         },
-        _ => return,
+        _ => {
+            eprintln!("Unknown notification type: {}", notification_type);
+            return;
+        }
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/src/lib.rs` around lines 531 - 538, The match's default arm
silently returns on unknown notification types; change `_ => return` to emit a
warning log including the unexpected notification type so failures are
discoverable. Locate the match that builds (title, text,
notification::NotificationIcon::Normal) (the arm handling `3` uses
i18n::get_string_fmt and i18n::get_string with app_name_str) and replace the
silent return with a log call (e.g., log::warn! or tracing::warn!) that includes
the unmatched value or enum variant, then return or handle as before.
rust_tray/src/menu_items.rs (1)

269-291: 建议:考虑缓存菜单项定义以避免重复分配

execute_handler 每次调用时都会通过 get_all_items() 分配新的 Vec。虽然菜单点击频率不高,但如果追求更好的性能,可以考虑使用 OnceCelllazy_static 缓存菜单项定义。

♻️ 可选的优化方案
use once_cell::sync::Lazy;

static ALL_ITEMS: Lazy<Vec<MenuItemInfo>> = Lazy::new(get_all_items);

pub fn execute_handler(item_id: &str) -> (bool, bool) {
    if let Some(item) = ALL_ITEMS.iter().find(|i| i.id == item_id) {
        // ... existing logic
    }
    (false, false)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/src/menu_items.rs` around lines 269 - 291, execute_handler
currently calls get_all_items() on every invocation which allocates a new Vec
each time; change to cache the menu definitions in a static lazy variable (e.g.,
use once_cell::sync::Lazy or OnceCell) so execute_handler looks up items from a
static ALL_ITEMS: Lazy<Vec<MenuItemInfo>> (initialized with get_all_items) and
then performs the same find/handler/trigger_action_for_id logic against
ALL_ITEMS instead of calling get_all_items() repeatedly.
cmake/targets/rust_tray.cmake (1)

8-11: Windows 目标平台硬编码为 x86_64,缺少 ARM64 支持

macOS 和 Linux 都有 ARM64 架构检测逻辑,但 Windows 部分硬编码为 x86_64-pc-windows-gnu。如果需要支持 Windows ARM64 设备,建议添加相应的架构检测:

♻️ 建议的修复方案
 if(WIN32)
     # Windows uses MinGW/UCRT toolchain - must use gnu target
-    set(RUST_TARGET "x86_64-pc-windows-gnu")
+    if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
+        set(RUST_TARGET "aarch64-pc-windows-gnu")
+    else()
+        set(RUST_TARGET "x86_64-pc-windows-gnu")
+    endif()
     set(RUST_LIB_NAME "libtray.a")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/targets/rust_tray.cmake` around lines 8 - 11, The Windows block
hardcodes RUST_TARGET to x86_64 and omits ARM64 support; update the WIN32 branch
to detect the host/process architecture (using CMAKE_HOST_SYSTEM_PROCESSOR or
CMAKE_SYSTEM_PROCESSOR) and set RUST_TARGET accordingly (e.g.,
"x86_64-pc-windows-gnu" for x86_64/AMD64 and "aarch64-pc-windows-msvc" or the
appropriate aarch64-pc-windows-gnu variant for ARM64), leaving RUST_LIB_NAME as
before; implement a simple conditional on the processor string (match
"AMD64"/"x86_64" vs "ARM64"/"aarch64") to select the correct target triple so
Windows ARM64 devices are supported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmake/targets/rust_tray.cmake`:
- Around line 63-69: The DEPENDS list for the rust tray target is incomplete:
files referenced by lib.rs (dialogs.rs, menu.rs, menu_items.rs, notification.rs)
are missing so CMake won't re-run when they change; update the DEPENDS block
that references ${RUST_TRAY_SOURCE_DIR}/Cargo.toml, build.rs, src/lib.rs,
src/i18n.rs, src/actions.rs to also include
${RUST_TRAY_SOURCE_DIR}/src/dialogs.rs, ${RUST_TRAY_SOURCE_DIR}/src/menu.rs,
${RUST_TRAY_SOURCE_DIR}/src/menu_items.rs and
${RUST_TRAY_SOURCE_DIR}/src/notification.rs (or replace the explicit list by a
file(GLOB ...) over ${RUST_TRAY_SOURCE_DIR}/src/*.rs if you prefer
auto-collecting files, noting GLOB caveats) so changes to those modules trigger
incremental rebuilds.

In `@rust_tray/build.rs`:
- Around line 14-22: 在 build.rs 的 Windows 平台块(#[cfg(target_os = "windows")] 内使用
println!("cargo:rustc-link-lib=...") 的地方,补上 CMake 中缺失的库:添加 println! 行分别为
oleaut32、uuid、bcrypt、ntdll、userenv、ws2_32 和 pthread(同时保留已存在的
user32、gdi32、shell32、ole32、comctl32,避免重复新增相同项);如果这些差异是有意为之,请改为在该模块或仓库 README
中明确记录何时使用 build.rs 链接而何时依赖 CMake,以避免构建时缺少依赖。

In `@rust_tray/include/rust_tray.h`:
- Around line 142-159:
头文件声明的三个深色模式函数(tray_enable_dark_mode、tray_force_dark_mode、tray_force_light_mode)在
lib.rs 中未导出,导致链接错误;为修复,在 lib.rs 中为每个函数添加对应的导出实现(使用 #[no_mangle] pub extern "C"
fn tray_enable_dark_mode(), #[no_mangle] pub extern "C" fn
tray_force_dark_mode(), #[no_mangle] pub extern "C" fn
tray_force_light_mode()),实现内部逻辑(可以调用现有初始化或平台函数以切换主题,或暂时做空实现以避免崩溃),并确保命名与头文件完全一致以正确导出符号。

In `@rust_tray/plan.md`:
- Line 29: The doc lists non-existent APIs tray_set_vdd_checked and
tray_set_vdd_enabled; update the runtime-updates line to match the real header
API by replacing those entries with the actual function name and signature
tray_update_vdd_menu(int can_create, int can_close, int is_persistent, int
is_active), and ensure the runtime update list includes tray_set_icon,
tray_set_tooltip, tray_update_vdd_menu, tray_set_locale, and
tray_show_notification so the documentation matches
rust_tray/include/rust_tray.h.

In `@rust_tray/README.md`:
- Around line 54-68: The README's architecture section is outdated: update the
directory tree to list the new Rust files (actions.rs, menu.rs, notification.rs,
dialogs.rs) under src and replace the note that mentions src/system_tray.cpp
with the current entry point src/system_tray_rust.cpp; also add a language tag
to the code fence (e.g., ```text or ```bash) to satisfy markdownlint MD040 so
the tree block is properly marked. Ensure the paragraph referencing
compatibility/third-party headers still applies or update it if the integration
changed, and keep all edits inside README.md to reflect the new filenames and
entry point.
- Around line 7-10: The README currently claims cross-platform support but the
implementation files rust_tray/src/dialogs.rs and rust_tray/src/notification.rs
and the Cargo.toml only enable Windows-specific APIs/dependencies; update
README.md to explicitly state this crate is Windows-only (or clearly document
that Linux/macOS are not yet implemented and list required preconditions for
those platforms), remove or mark as Windows-only any Linux/macOS artifact paths,
and update the corresponding lines referenced (including lines 28-31) to avoid
implying full cross-platform support; reference rust_tray/src/dialogs.rs,
rust_tray/src/notification.rs, and rust_tray/Cargo.toml when making the README
changes.

In `@rust_tray/src/i18n.rs`:
- Around line 262-271: The test_get_string assertions for
StringKey::OpenSunshine don't match the translation table values used by
get_string; update the test or the translations so they align: either change the
expected values in test_get_string to "Open GUI", "打开基地面板", and "GUIを開く" to
match the current entries, or modify the translation entries referenced by
get_string/StringKey::OpenSunshine to "Open Sunshine", "打开 Sunshine", and
"Sunshineを開く"; ensure you update the set_locale calls and assertions in
test_get_string accordingly so the test and get_string behavior are consistent.

In `@rust_tray/src/lib.rs`:
- Around line 47-49: The unsafe impls for TrayState rely on the comment "only
accessed from the main thread" but offer no enforcement; update the code to
either document this constraint in the C header and FFI docs (clearly state that
all FFI calls touching TrayState must be made from the main thread) and/or add a
runtime guard: record std::thread::ThreadId (e.g., main_thread_id) in TrayState
at initialization and validate it at the start of every extern "C" function that
accesses TrayState (return an error or abort if the current thread id differs).
Reference the unsafe impl Send/Sync for TrayState and the FFI entry points that
use TrayState to locate where to add the ThreadId field and per-call checks.

In `@rust_tray/src/menu.rs`:
- Around line 24-28: CREATED_ITEMS 是线程局部 (thread_local!),导致在非创建线程(如
tray_update_vdd_menu() 被分离线程调用时)看不到状态更新并 no-op;将状态修改封送回 tray/UI 线程。改法:替换或补充
CREATED_ITEMS 的 TLS 存储,改用全局线程安全结构(例如 lazy_static/once_cell +
Arc<Mutex<HashMap<...>>> 或者锁/并发 map)或在所有 setter 中通过一个跨线程 channel 将“变更事件/闭包”发送到
UI/tray 线程并在 tray_update_vdd_menu() 或 UI 事件循环处消费并应用这些变更;确保所有读取/写入点(涉及
CREATED_ITEMS、相关 setter)改为使用该线程安全通道/全局结构以保证跨线程更新生效。

In `@src/system_tray_rust.cpp`:
- Around line 340-344: 函数 update_tray_vmonitor_checked 的 checked
参数被忽略:你要么把它改为真正使用传入的 checked 值去设置对应的菜单项(在函数内调用更新单个 VMonitor 复选框的逻辑,避免仅调用
update_vdd_menu_state),要么将函数签名去掉 checked 并重命名为 refresh_tray_vmonitor 或 similar
表示仅刷新当前状态;更新调用处以匹配新签名并确保唯一定位符为 update_tray_vmonitor_checked /
update_vdd_menu_state。
- Around line 252-257: process_tray_events currently calls tray_loop(0) which
busy-polls; change it so that if the tray runs on its own thread you call the
blocking form of the loop (use tray_loop with the blocking argument, e.g.,
tray_loop(-1) or the API's blocking mode) inside process_tray_events when
tray_initialized indicates a dedicated tray thread, otherwise keep non-blocking
tray_loop(0) but detect the "no events" return value and call
std::this_thread::sleep_for(std::chrono::milliseconds(n)) or
std::this_thread::yield() before returning to avoid busy-wait; update callers
that repeatedly call process_tray_events (they use process_tray_events in tight
loops) to rely on the blocking mode when appropriate or accept the short
sleep/yield when using non-blocking mode.
- Around line 186-205: terminate_gui_processes currently force-kills every
process named "sunshine-gui.exe"; instead restrict termination to only the GUI
instances our process actually launched or those in the same session. Update
terminate_gui_processes to prefer using a known GUI PID (e.g., stored gui_pid)
and only call OpenProcess/TerminateProcess for that PID; if the PID is not
available, filter process entries by SessionId (use ProcessIdToSessionId and
compare to the current session), and further verify the process image path
matches the expected executable (use QueryFullProcessImageNameW) and/or that the
parent PID matches our process (via CreateToolhelp32Snapshot/PROCESSENTRY32W
parent check) before calling OpenProcess/TerminateProcess; keep the current
CreateToolhelp32Snapshot/PROCESSENTRY32W loop but add these checks to avoid
killing unrelated instances.

---

Nitpick comments:
In `@cmake/targets/rust_tray.cmake`:
- Around line 8-11: The Windows block hardcodes RUST_TARGET to x86_64 and omits
ARM64 support; update the WIN32 branch to detect the host/process architecture
(using CMAKE_HOST_SYSTEM_PROCESSOR or CMAKE_SYSTEM_PROCESSOR) and set
RUST_TARGET accordingly (e.g., "x86_64-pc-windows-gnu" for x86_64/AMD64 and
"aarch64-pc-windows-msvc" or the appropriate aarch64-pc-windows-gnu variant for
ARM64), leaving RUST_LIB_NAME as before; implement a simple conditional on the
processor string (match "AMD64"/"x86_64" vs "ARM64"/"aarch64") to select the
correct target triple so Windows ARM64 devices are supported.

In `@rust_tray/.gitignore`:
- Around line 1-2: 当前 .gitignore 忽略了 Cargo.lock,但因为 rust_tray
是作为静态库/可执行程序的一部分被集成到 C++ 应用中,应提交 Cargo.lock 以保证依赖可复现;请在 rust_tray 的 .gitignore
中移除 Cargo.lock(或直接将 Cargo.lock 添加到仓库并提交),并在以后更新依赖后保持同步提交更新的 Cargo.lock(定位到
.gitignore 和 Cargo.lock 文件以修改/提交)。

In `@rust_tray/src/i18n.rs`:
- Around line 237-240: The function in rust_tray/src/i18n.rs that currently ends
by returning an empty string (the final `""`) should log a warning when a
translation key is missing instead of silently returning empty; update the
function (e.g., the translate/t or equivalent lookup function that contains the
`return ""`) to call your logging facility (warn! or tracing::warn!) with the
missing key and context (requested locale(s) and/or the list of available
languages) before returning the empty string so missing translations are
discoverable during development.

In `@rust_tray/src/lib.rs`:
- Around line 531-538: The match's default arm silently returns on unknown
notification types; change `_ => return` to emit a warning log including the
unexpected notification type so failures are discoverable. Locate the match that
builds (title, text, notification::NotificationIcon::Normal) (the arm handling
`3` uses i18n::get_string_fmt and i18n::get_string with app_name_str) and
replace the silent return with a log call (e.g., log::warn! or tracing::warn!)
that includes the unmatched value or enum variant, then return or handle as
before.

In `@rust_tray/src/menu_items.rs`:
- Around line 269-291: execute_handler currently calls get_all_items() on every
invocation which allocates a new Vec each time; change to cache the menu
definitions in a static lazy variable (e.g., use once_cell::sync::Lazy or
OnceCell) so execute_handler looks up items from a static ALL_ITEMS:
Lazy<Vec<MenuItemInfo>> (initialized with get_all_items) and then performs the
same find/handler/trigger_action_for_id logic against ALL_ITEMS instead of
calling get_all_items() repeatedly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38de3456-3cbb-49fc-8c9c-4250e84b204e

📥 Commits

Reviewing files that changed from the base of the PR and between d74f9ed and 3af156f.

📒 Files selected for processing (20)
  • .github/workflows/main.yml
  • cmake/compile_definitions/common.cmake
  • cmake/compile_definitions/windows.cmake
  • cmake/targets/rust_tray.cmake
  • rust_tray/.cargo/config.toml
  • rust_tray/.gitignore
  • rust_tray/Cargo.toml
  • rust_tray/README.md
  • rust_tray/build.rs
  • rust_tray/include/rust_tray.h
  • rust_tray/plan.md
  • rust_tray/src/actions.rs
  • rust_tray/src/dialogs.rs
  • rust_tray/src/i18n.rs
  • rust_tray/src/lib.rs
  • rust_tray/src/menu.rs
  • rust_tray/src/menu_items.rs
  • rust_tray/src/notification.rs
  • src/system_tray.h
  • src/system_tray_rust.cpp

Comment on lines +63 to +69
DEPENDS
${RUST_TRAY_SOURCE_DIR}/Cargo.toml
${RUST_TRAY_SOURCE_DIR}/build.rs
${RUST_TRAY_SOURCE_DIR}/src/lib.rs
${RUST_TRAY_SOURCE_DIR}/src/i18n.rs
${RUST_TRAY_SOURCE_DIR}/src/actions.rs
VERBATIM
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

DEPENDS 列表不完整,可能导致增量构建失效

根据 lib.rs 中声明的模块,src/ 目录下还有 dialogs.rsmenu.rsmenu_items.rsnotification.rs,但这些文件未包含在依赖列表中。当这些文件修改时,CMake 不会触发重新构建。

🔧 建议的修复方案
     DEPENDS
         ${RUST_TRAY_SOURCE_DIR}/Cargo.toml
         ${RUST_TRAY_SOURCE_DIR}/build.rs
         ${RUST_TRAY_SOURCE_DIR}/src/lib.rs
         ${RUST_TRAY_SOURCE_DIR}/src/i18n.rs
         ${RUST_TRAY_SOURCE_DIR}/src/actions.rs
+        ${RUST_TRAY_SOURCE_DIR}/src/dialogs.rs
+        ${RUST_TRAY_SOURCE_DIR}/src/menu.rs
+        ${RUST_TRAY_SOURCE_DIR}/src/menu_items.rs
+        ${RUST_TRAY_SOURCE_DIR}/src/notification.rs
     VERBATIM

或者使用 file(GLOB ...) 自动收集所有 .rs 文件(但需注意 GLOB 在新增文件时的限制)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmake/targets/rust_tray.cmake` around lines 63 - 69, The DEPENDS list for the
rust tray target is incomplete: files referenced by lib.rs (dialogs.rs, menu.rs,
menu_items.rs, notification.rs) are missing so CMake won't re-run when they
change; update the DEPENDS block that references
${RUST_TRAY_SOURCE_DIR}/Cargo.toml, build.rs, src/lib.rs, src/i18n.rs,
src/actions.rs to also include ${RUST_TRAY_SOURCE_DIR}/src/dialogs.rs,
${RUST_TRAY_SOURCE_DIR}/src/menu.rs, ${RUST_TRAY_SOURCE_DIR}/src/menu_items.rs
and ${RUST_TRAY_SOURCE_DIR}/src/notification.rs (or replace the explicit list by
a file(GLOB ...) over ${RUST_TRAY_SOURCE_DIR}/src/*.rs if you prefer
auto-collecting files, noting GLOB caveats) so changes to those modules trigger
incremental rebuilds.

Comment on lines +14 to +22
// Platform-specific linker flags
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=user32");
println!("cargo:rustc-link-lib=gdi32");
println!("cargo:rustc-link-lib=shell32");
println!("cargo:rustc-link-lib=ole32");
println!("cargo:rustc-link-lib=comctl32");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 验证 CMake 中定义的平台库是否与 build.rs 重复
echo "=== CMake RUST_TRAY_PLATFORM_LIBS ==="
rg -A 20 'RUST_TRAY_PLATFORM_LIBS' cmake/targets/rust_tray.cmake

echo ""
echo "=== build.rs link-lib ==="
cat rust_tray/build.rs

Repository: AlkaidLab/foundation-sunshine

Length of output: 1734


build.rs 中的 Windows 库链接不完整且存在重复

build.rs 中声明的 Windows 库集合与 CMake 配置不一致。虽然 build.rs 中的 5 个库(user32、gdi32、shell32、ole32、comctl32)确实在 CMake 中也被链接,形成重复,但更重要的问题是 build.rs 缺少 CMake 中指定的 7 个库:oleaut32、uuid、bcrypt、ntdll、userenv、ws2_32、pthread。

当通过 cargo 独立构建此 Rust 库时,将缺少这些关键依赖,可能导致链接或运行时失败。建议:

  • 更新 build.rs 以包含 CMake 中定义的所有库,或
  • 明确记录两者适用的场景(如果设计上有意区分)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/build.rs` around lines 14 - 22, 在 build.rs 的 Windows
平台块(#[cfg(target_os = "windows")] 内使用 println!("cargo:rustc-link-lib=...")
的地方,补上 CMake 中缺失的库:添加 println! 行分别为 oleaut32、uuid、bcrypt、ntdll、userenv、ws2_32 和
pthread(同时保留已存在的
user32、gdi32、shell32、ole32、comctl32,避免重复新增相同项);如果这些差异是有意为之,请改为在该模块或仓库 README
中明确记录何时使用 build.rs 链接而何时依赖 CMake,以避免构建时缺少依赖。

Comment on lines +142 to +159
/**
* @brief Enable dark mode for context menus (follow system setting)
*
* Call this before creating menus. The menu will automatically
* follow the system's dark/light mode setting.
* Note: Only effective on Windows 10 1903+ and Windows 11.
*/
void tray_enable_dark_mode(void);

/**
* @brief Force dark mode for context menus
*/
void tray_force_dark_mode(void);

/**
* @brief Force light mode for context menus
*/
void tray_force_light_mode(void);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 搜索深色模式函数的实现
echo "=== Searching for dark mode function implementations ==="
rg -n "tray_enable_dark_mode|tray_force_dark_mode|tray_force_light_mode" --type rust

echo ""
echo "=== Checking for #[no_mangle] exports ==="
rg -n "#\[no_mangle\]" rust_tray/src/ -A 2 | grep -E "(dark_mode|light_mode)"

Repository: AlkaidLab/foundation-sunshine

Length of output: 172


🏁 Script executed:

# Check project structure
find rust_tray -type f -name "*.c" -o -name "*.cpp" -o -name "*.h" | head -20

# Search more broadly for function names (including C files)
echo "=== Searching all source files ==="
rg -n "enable_dark_mode|force_dark_mode|force_light_mode" --type c --type cpp

# Check if header file references these from elsewhere
echo ""
echo "=== Checking if functions are mentioned anywhere ==="
rg -n "tray_enable_dark_mode|tray_force_dark_mode|tray_force_light_mode" -i

Repository: AlkaidLab/foundation-sunshine

Length of output: 597


🏁 Script executed:

# List all files in rust_tray/src to understand the structure
echo "=== Files in rust_tray/src ==="
find rust_tray/src -type f -name "*.rs"

# Check all #[no_mangle] pub extern "C" exports to see what's implemented
echo ""
echo "=== All #[no_mangle] exports in Rust ==="
rg -n "#\[no_mangle\]" -A 1 rust_tray/src/

# Check the full lib.rs to see if there are any mode-related functions
echo ""
echo "=== Checking for any mode-related functions (case-insensitive) ==="
rg -i "mode|theme|dark|light" rust_tray/src/lib.rs -n

Repository: AlkaidLab/foundation-sunshine

Length of output: 1438


🏁 Script executed:

# Check the total count of #[no_mangle] exports to be comprehensive
echo "=== Total count of #[no_mangle] functions ==="
rg -c "#\[no_mangle\]" rust_tray/src/lib.rs

# Get a list of all exported function names
echo ""
echo "=== All exported C function names ==="
rg "#\[no_mangle\]" -A 2 rust_tray/src/lib.rs | grep "pub.*extern" | sed 's/.*fn //' | sed 's/(.*//'

Repository: AlkaidLab/foundation-sunshine

Length of output: 316


rust_tray.h 中声明的深色模式函数缺少实现

头文件在第 149、154、159 行声明了三个函数,但它们在 lib.rs 中均未实现:

  • tray_enable_dark_mode
  • tray_force_dark_mode
  • tray_force_light_mode

实际的导出函数(通过 #[no_mangle] pub extern "C")仅包含 9 个函数:tray_init_extray_looptray_exittray_set_icontray_set_tooltiptray_update_vdd_menutray_set_localetray_show_notificationtray_show_localized_notification。这些声明的函数需要实现以避免链接错误。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/include/rust_tray.h` around lines 142 - 159,
头文件声明的三个深色模式函数(tray_enable_dark_mode、tray_force_dark_mode、tray_force_light_mode)在
lib.rs 中未导出,导致链接错误;为修复,在 lib.rs 中为每个函数添加对应的导出实现(使用 #[no_mangle] pub extern "C"
fn tray_enable_dark_mode(), #[no_mangle] pub extern "C" fn
tray_force_dark_mode(), #[no_mangle] pub extern "C" fn
tray_force_light_mode()),实现内部逻辑(可以调用现有初始化或平台函数以切换主题,或暂时做空实现以避免崩溃),并确保命名与头文件完全一致以正确导出符号。

运行时与 API 变化:
- 初始化:推荐使用 `tray_init_ex(icon_normal, icon_playing, icon_pausing, icon_locked, tooltip, locale, callback)`。
- 事件循环:使用 `tray_loop(blocking)` 驱动;返回 -1 表示要求退出。可在单线程或分线程中调用(包装器提供线程化入口)。
- 运行时更新:`tray_set_icon`、`tray_set_tooltip`、`tray_set_vdd_checked`、`tray_set_vdd_enabled`、`tray_set_locale`、`tray_show_notification`。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

API 文档与实际接口不一致

文档中提到的 tray_set_vdd_checkedtray_set_vdd_enabled 函数在 rust_tray/include/rust_tray.h 中并不存在。根据头文件,实际的 API 是 tray_update_vdd_menu(int can_create, int can_close, int is_persistent, int is_active)

建议更新文档以反映实际的 API 接口。

📝 建议的修改
-- 运行时更新:`tray_set_icon`、`tray_set_tooltip`、`tray_set_vdd_checked`、`tray_set_vdd_enabled`、`tray_set_locale`、`tray_show_notification`。
+- 运行时更新:`tray_set_icon`、`tray_set_tooltip`、`tray_update_vdd_menu`、`tray_set_locale`、`tray_show_notification`、`tray_show_localized_notification`。
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 运行时更新:`tray_set_icon``tray_set_tooltip``tray_set_vdd_checked``tray_set_vdd_enabled``tray_set_locale``tray_show_notification`
- 运行时更新:`tray_set_icon``tray_set_tooltip``tray_update_vdd_menu``tray_set_locale``tray_show_notification``tray_show_localized_notification`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/plan.md` at line 29, The doc lists non-existent APIs
tray_set_vdd_checked and tray_set_vdd_enabled; update the runtime-updates line
to match the real header API by replacing those entries with the actual function
name and signature tray_update_vdd_menu(int can_create, int can_close, int
is_persistent, int is_active), and ensure the runtime update list includes
tray_set_icon, tray_set_tooltip, tray_update_vdd_menu, tray_set_locale, and
tray_show_notification so the documentation matches
rust_tray/include/rust_tray.h.

Comment on lines +7 to +10
- Cross-platform support (Windows, Linux, macOS)
- Compatible C API matching the original `tray.h` header
- Uses the `tray-icon` Rust crate for the underlying implementation
- Better maintainability and reduced cross-platform adaptation code
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

README 仍把当前实现写成跨平台。

rust_tray/src/dialogs.rsrust_tray/src/notification.rs 已经直接依赖 Windows API / WinRT,rust_tray/Cargo.toml 也只在 cfg(windows) 下声明了这部分依赖,但这里仍写着 Linux/macOS 支持并列出对应产物路径。建议改成明确的 Windows-only 说明,或者把尚未实现的前提条件写清楚,避免误导手动构建和集成。

Also applies to: 28-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/README.md` around lines 7 - 10, The README currently claims
cross-platform support but the implementation files rust_tray/src/dialogs.rs and
rust_tray/src/notification.rs and the Cargo.toml only enable Windows-specific
APIs/dependencies; update README.md to explicitly state this crate is
Windows-only (or clearly document that Linux/macOS are not yet implemented and
list required preconditions for those platforms), remove or mark as Windows-only
any Linux/macOS artifact paths, and update the corresponding lines referenced
(including lines 28-31) to avoid implying full cross-platform support; reference
rust_tray/src/dialogs.rs, rust_tray/src/notification.rs, and
rust_tray/Cargo.toml when making the README changes.

Comment on lines +47 to +49
// Safety: TrayState is only accessed from the main thread
unsafe impl Send for TrayState {}
unsafe impl Sync for TrayState {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

线程安全假设需要文档说明或运行时保证

TrayStateunsafe impl Sendunsafe impl Sync 依赖于"仅从主线程访问"的假设。然而,这个约束仅通过注释说明,没有编译时或运行时保证。

如果 C++ 端从多个线程调用这些 FFI 函数,可能会导致未定义行为。建议:

  1. 在 C 头文件中明确记录线程安全约束
  2. 或者考虑添加运行时检查(如 std::thread::current().id() 检查)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/src/lib.rs` around lines 47 - 49, The unsafe impls for TrayState
rely on the comment "only accessed from the main thread" but offer no
enforcement; update the code to either document this constraint in the C header
and FFI docs (clearly state that all FFI calls touching TrayState must be made
from the main thread) and/or add a runtime guard: record std::thread::ThreadId
(e.g., main_thread_id) in TrayState at initialization and validate it at the
start of every extern "C" function that accesses TrayState (return an error or
abort if the current thread id differs). Reference the unsafe impl Send/Sync for
TrayState and the FFI entry points that use TrayState to locate where to add the
ThreadId field and per-call checks.

Comment on lines +24 to +28
// Thread-local storage for actual muda items (not thread-safe)
thread_local! {
static CREATED_ITEMS: std::cell::RefCell<HashMap<String, CreatedItem>> =
std::cell::RefCell::new(HashMap::new());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CREATED_ITEMS 会让跨线程菜单更新静默失效。

这些 setter 只能看到创建菜单那个线程里的 item,但 src/system_tray_rust.cpp 的 Line 93-105 会在分离线程里再次调用 tray_update_vdd_menu()。10 秒冷却结束后的那次更新会落在空的 TLS 上直接 no-op,VDD 菜单就可能一直停留在 cooldown 状态。这里需要把状态变更封送回 tray/UI 线程再应用。

Also applies to: 64-96, 202-226

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust_tray/src/menu.rs` around lines 24 - 28, CREATED_ITEMS 是线程局部
(thread_local!),导致在非创建线程(如 tray_update_vdd_menu() 被分离线程调用时)看不到状态更新并
no-op;将状态修改封送回 tray/UI 线程。改法:替换或补充 CREATED_ITEMS 的 TLS 存储,改用全局线程安全结构(例如
lazy_static/once_cell + Arc<Mutex<HashMap<...>>> 或者锁/并发 map)或在所有 setter 中通过一个跨线程
channel 将“变更事件/闭包”发送到 UI/tray 线程并在 tray_update_vdd_menu() 或 UI
事件循环处消费并应用这些变更;确保所有读取/写入点(涉及 CREATED_ITEMS、相关 setter)改为使用该线程安全通道/全局结构以保证跨线程更新生效。

Comment on lines +186 to +205
void terminate_gui_processes() {
#ifdef _WIN32
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (Process32FirstW(snapshot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, L"sunshine-gui.exe") == 0) {
HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
if (process_handle != NULL) {
TerminateProcess(process_handle, 0);
CloseHandle(process_handle);
}
}
} while (Process32NextW(snapshot, &pe32));
}
CloseHandle(snapshot);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

不要按进程名全局强杀 sunshine-gui.exe

这里会终止系统里所有同名进程,包含其他用户会话、手动启动的实例,甚至未来可能的第二个 Sunshine 实例。退出托盘时最好只结束当前进程实际拉起的 GUI PID,至少再按 SessionId、父进程或可执行路径做过滤。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/system_tray_rust.cpp` around lines 186 - 205, terminate_gui_processes
currently force-kills every process named "sunshine-gui.exe"; instead restrict
termination to only the GUI instances our process actually launched or those in
the same session. Update terminate_gui_processes to prefer using a known GUI PID
(e.g., stored gui_pid) and only call OpenProcess/TerminateProcess for that PID;
if the PID is not available, filter process entries by SessionId (use
ProcessIdToSessionId and compare to the current session), and further verify the
process image path matches the expected executable (use
QueryFullProcessImageNameW) and/or that the parent PID matches our process (via
CreateToolhelp32Snapshot/PROCESSENTRY32W parent check) before calling
OpenProcess/TerminateProcess; keep the current
CreateToolhelp32Snapshot/PROCESSENTRY32W loop but add these checks to avoid
killing unrelated instances.

Comment on lines +252 to +257
int process_tray_events() {
if (!tray_initialized) {
return -1;
}
return tray_loop(0); // Non-blocking
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

非阻塞 tray_loop(0) 会把事件循环变成忙轮询。

process_tray_events() 在无消息时仍返回 0,而本文件的 Line 284-285 和 src/main.cpp 的 Line 110-117 都在无休眠的 while 里反复调用它。空闲时会持续占用 CPU。若已经有独立 tray 线程,建议改成阻塞模式;否则至少在无消息时 sleep_for / yield

Also applies to: 279-290

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/system_tray_rust.cpp` around lines 252 - 257, process_tray_events
currently calls tray_loop(0) which busy-polls; change it so that if the tray
runs on its own thread you call the blocking form of the loop (use tray_loop
with the blocking argument, e.g., tray_loop(-1) or the API's blocking mode)
inside process_tray_events when tray_initialized indicates a dedicated tray
thread, otherwise keep non-blocking tray_loop(0) but detect the "no events"
return value and call std::this_thread::sleep_for(std::chrono::milliseconds(n))
or std::this_thread::yield() before returning to avoid busy-wait; update callers
that repeatedly call process_tray_events (they use process_tray_events in tight
loops) to rely on the blocking mode when appropriate or accept the short
sleep/yield when using non-blocking mode.

Comment on lines +340 to +344
void update_tray_vmonitor_checked(int checked) {
if (!tray_initialized) return;
// Use the unified VDD menu update function
update_vdd_menu_state();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

checked 参数在这里被完全忽略了。

这个接口名和签名都表示调用方可以显式更新复选框状态,但实现只是重新计算整套 VDD 状态并无视传入值。若这里本意只是“刷新当前状态”,建议删除这个参数并重命名接口;否则应该按参数更新对应菜单项。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/system_tray_rust.cpp` around lines 340 - 344, 函数
update_tray_vmonitor_checked 的 checked 参数被忽略:你要么把它改为真正使用传入的 checked
值去设置对应的菜单项(在函数内调用更新单个 VMonitor 复选框的逻辑,避免仅调用 update_vdd_menu_state),要么将函数签名去掉
checked 并重命名为 refresh_tray_vmonitor 或 similar 表示仅刷新当前状态;更新调用处以匹配新签名并确保唯一定位符为
update_tray_vmonitor_checked / update_vdd_menu_state。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants