//! 托盘常驻:图标 + 菜单[显示主窗口/退出]。关窗口不退出,托盘退出才真正结束进程。 //! //! - 主窗口 CloseRequested 被 lib.rs 拦截(prevent_close + hide),进程常驻托盘 //! - 托盘菜单「显示主窗口」唤起 / 「退出」app.exit(0) 真正结束 //! - TrayHandle 经 app.manage 保活,防托盘图标被 GC 移除 use tauri::{ AppHandle, Manager, menu::{Menu, MenuItem}, tray::TrayIconBuilder, }; /// managed state 保活,防 GC 移除托盘图标。 /// 字段不读仅用于持有 TrayIcon(保活);零调用方≠垃圾,预留保留(对齐 state.rs releases 标法)。 #[allow(dead_code)] pub struct TrayHandle(pub tauri::tray::TrayIcon); /// 创建托盘:图标 + 菜单(显示主窗口/退出)。 pub fn setup_tray(app: &AppHandle) -> tauri::Result<()> { let show = MenuItem::with_id(app, "show", "显示主窗口", true, None::<&str>)?; let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?; let menu = Menu::with_items(app, &[&show, &quit])?; // 复用窗口图标(tauri.conf.json bundle.icon 已配置),缺则 panic 提示配置 let icon = app .default_window_icon() .cloned() .expect("窗口图标缺失(tauri.conf.json icons 已配置)"); let tray = TrayIconBuilder::with_id("devflow-tray") .icon(icon) .tooltip("DevFlow (MCP server running)") .menu(&menu) .show_menu_on_left_click(false) .on_menu_event(|app, event| match event.id().as_ref() { "show" => show_main(app), "quit" => app.exit(0), // 真正退出 _ => {} }) .build(app)?; app.manage(TrayHandle(tray)); Ok(()) } /// 唤起主窗口:show + unminimize + set_focus(单实例二次启动 / 托盘菜单共用)。 pub(crate) fn show_main(app: &AppHandle) { if let Some(w) = app.get_webview_window("main") { let _ = w.show(); let _ = w.unminimize(); let _ = w.set_focus(); } }