#[cfg(desktop)] use tauri::{ image::Image, menu::{CheckMenuItemBuilder, Menu, MenuItemBuilder}, tray::{MouseButton, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent}, AppHandle, Emitter, Manager, }; #[cfg(desktop)] use tauri_plugin_autostart::ManagerExt; #[cfg(desktop)] use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; #[cfg(desktop)] fn load_tray_icon() -> Image<'static> { let icon_data = include_bytes!("../icons/icon.ico"); // Parse ICO file and extract the largest icon let ico_file = ico::IconDir::read(std::io::Cursor::new(icon_data)) .expect("Failed to read ICO file"); let entry = &ico_file.entries()[0]; let image = entry.decode().expect("Failed to decode ICO entry"); Image::new_owned(image.rgba_data().to_vec(), image.width(), image.height()) } #[cfg(desktop)] pub fn create_tray_menu(app_handle: &AppHandle) -> Result { let autostart_enabled = app_handle.autolaunch().is_enabled().unwrap_or(false); // Create menu items let show = MenuItemBuilder::with_id("show", "显示").build(app_handle)?; let hide = MenuItemBuilder::with_id("hide", "隐藏").build(app_handle)?; let autostart = CheckMenuItemBuilder::with_id("autostart", "开机自启") .checked(autostart_enabled) .build(app_handle)?; let quit = MenuItemBuilder::with_id("quit", "退出").build(app_handle)?; let about = MenuItemBuilder::with_id("about", "关于").build(app_handle)?; // Clone handles for moving into closures let autostart_for_event = autostart.clone(); let autostart_for_sync = autostart.clone(); // Create the menu let tray_menu = Menu::with_items(app_handle, &[&show, &hide, &autostart, &quit, &about])?; // Get app version let version = app_handle.package_info().version.to_string(); // Create the tray icon let icon = load_tray_icon(); let tray_icon = TrayIconBuilder::new() .icon(icon) .menu(&tray_menu) .tooltip(&format!("News Classifier v{}", version)) .on_menu_event(move |app_handle, event| { match event.id().as_ref() { "quit" => { std::process::exit(0); } "show" => { if let Some(window) = app_handle.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); } } "hide" => { if let Some(window) = app_handle.get_webview_window("main") { let _ = window.hide(); } } "autostart" => { let autolaunch_manager = app_handle.autolaunch(); let autostart_item = autostart_for_event.clone(); if autolaunch_manager.is_enabled().unwrap_or(false) { if let Err(e) = autolaunch_manager.disable() { eprintln!("Failed to disable autostart: {}", e); } else { let _ = autostart_item.set_checked(false); let _ = app_handle.emit("autostart-changed", false); } } else { if let Err(e) = autolaunch_manager.enable() { eprintln!("Failed to enable autostart: {}", e); } else { let _ = autostart_item.set_checked(true); let _ = app_handle.emit("autostart-changed", true); } } } "about" => { if let Some(window) = app_handle.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); window .dialog() .message(format!("应用名称: News Classifier\n版本: {}", version)) .title("关于 News Classifier") .buttons(MessageDialogButtons::Ok) .show(|_| {}); } } _ => {} } }) .on_tray_icon_event({ let app_handle = app_handle.clone(); move |_tray_id, event| { if let TrayIconEvent::Click { button, button_state, .. } = event { if button == MouseButton::Left && button_state == MouseButtonState::Up { if let Some(window) = app_handle.get_webview_window("main") { match window.is_visible() { Ok(true) => { let _ = window.hide(); } Ok(false) => { let _ = window.show(); let _ = window.set_focus(); } Err(e) => eprintln!("Failed to get window visibility: {}", e), } } } } } }) .build(app_handle)?; // Spawn a background task to sync autostart state let app_handle = app_handle.clone(); tauri::async_runtime::spawn(async move { loop { // Sync autostart menu item state with actual system state if let Ok(is_enabled) = app_handle.autolaunch().is_enabled() { let is_checked = autostart_for_sync.is_checked().unwrap_or(false); if is_checked != is_enabled { let _ = autostart_for_sync.set_checked(is_enabled); } } tokio::time::sleep(std::time::Duration::from_secs(2)).await; } }); Ok(tray_icon) }