diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e1901a532..c96b752e5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1460,6 +1460,11 @@ pub fn run() { tauri::async_runtime::spawn(async move { save_window_state_before_exit(&app_handle); cleanup_before_exit(&app_handle).await; + // 先于 std::process::exit 显式移除托盘图标。 + // 进程直接退出时 Tauri 运行时不走正常 Drop 流程, + // 不会向 Windows Shell 发送 NIM_DELETE,导致已退出的进程 + // 注册的图标仍残留在系统托盘(鼠标悬停 Shell 才会重绘发现进程已死)。 + remove_tray_icon_before_exit(&app_handle); log::info!("清理完成,退出应用"); // 短暂等待确保所有 I/O 操作(如数据库写入)刷新到磁盘 @@ -1607,6 +1612,26 @@ pub async fn cleanup_before_exit(app_handle: &tauri::AppHandle) { } } +/// 主动从系统托盘移除托盘图标。 +/// +/// `std::process::exit` 会绕过 Tauri 运行时,触发不了 `TrayIcon::drop()`, +/// 也就不会向 Windows Shell 发 `NIM_DELETE`。结果是进程退出后托盘里 +/// 仍保留一个死图标的缓存占位(Shell 不会主动重绘,需要鼠标悬停才刷新)。 +/// +/// 通过 `set_visible(false)` 走 `WM_USER_HIDE_TRAYICON` 消息路径, +/// 触发 tray-icon 内部的 `remove_tray_icon` → `Shell_NotifyIconW(NIM_DELETE)`, +/// 在进程结束前干净地把图标摘掉。其它平台 `set_visible(false)` 也是 +/// 正常的隐藏/移除语义,作为跨平台兜底也安全。 +fn remove_tray_icon_before_exit(app_handle: &tauri::AppHandle) { + if let Some(tray) = app_handle.tray_by_id(tray::TRAY_ID) { + if let Err(e) = tray.set_visible(false) { + log::warn!("退出时移除托盘图标失败: {e}"); + } else { + log::info!("已显式从系统托盘移除图标"); + } + } +} + // ============================================================ // 启动时恢复代理状态 // ============================================================