use crate::{AppState, BandwidthLevel}; use std::sync::atomic::Ordering; use std::time::Duration; #[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}; // Auxiliary function: format duration fn format_duration(seconds: u64) -> String { let hours = seconds / 3600; let minutes = (seconds % 3600) / 60; let secs = seconds % 60; format!("{:02}:{:02}:{:02}", hours, minutes, secs) } // Auxiliary function: format bytes fn format_bytes(bytes: u64) -> String { const KB: u64 = 1000; const MB: u64 = 1000 * KB; const GB: u64 = 1000 * MB; if bytes >= GB { format!("{:.2} GB", bytes as f64 / GB as f64) } else if bytes >= MB { format!("{:.2} MB", bytes as f64 / MB as f64) } else if bytes >= KB { format!("{:.2} KB", bytes as f64 / KB as f64) } else { format!("{} B", bytes) } } // Auxiliary function: convert bandwidth level to Chinese string fn level_to_str(level: &BandwidthLevel) -> &str { match level { BandwidthLevel::Off => "关闭", BandwidthLevel::Micro => "微速", BandwidthLevel::Mini => "慢速", BandwidthLevel::Low => "轻度", BandwidthLevel::Medium => "中度", BandwidthLevel::High => "高速", BandwidthLevel::Turbo => "涡轮", BandwidthLevel::Ultra => "超速", BandwidthLevel::Extreme => "极限", } } #[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", "Show").build(app_handle)?; let hide = MenuItemBuilder::with_id("hide", "Hide").build(app_handle)?; let autostart = CheckMenuItemBuilder::with_id("autostart", "开机自启") .checked(autostart_enabled) .build(app_handle)?; let quit = MenuItemBuilder::with_id("quit", "Quit").build(app_handle)?; let about = MenuItemBuilder::with_id("about", "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 = Image::from_bytes(include_bytes!("../icons/icon.ico"))?; let tray_icon = TrayIconBuilder::new() .icon(icon) .menu(&tray_menu) .tooltip("BandHot") .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") { window.show().unwrap(); window.set_focus().unwrap(); } } "hide" => { if let Some(window) = app_handle.get_webview_window("main") { window.hide().unwrap(); } } "autostart" => { let autolaunch_manager = app_handle.autolaunch(); if autolaunch_manager.is_enabled().unwrap_or(false) { if let Err(e) = autolaunch_manager.disable() { eprintln!("Failed to disable autostart: {}", e); } else { autostart_for_event.set_checked(false).unwrap(); app_handle.emit("autostart-changed", false).unwrap(); } } else { if let Err(e) = autolaunch_manager.enable() { eprintln!("Failed to enable autostart: {}", e); } else { autostart_for_event.set_checked(true).unwrap(); app_handle.emit("autostart-changed", true).unwrap(); } } } "about" => { if let Some(window) = app_handle.get_webview_window("main") { window.show().unwrap(); window.set_focus().unwrap(); window .dialog() .message("应用名称: BandHog\n版本: 1.0.0\n作者: shenjianZ\nGitee: https://gitee.com/mingjianyeying/BandHot") .title("关于 BandHog") .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)?; let app_handle = app_handle.clone(); let tray_icon_clone = tray_icon.clone(); tauri::async_runtime::spawn(async move { loop { // Update tooltip let state = app_handle.state::(); let controller = state.bandwidth_controller.read().await; let is_running = state.is_running.load(Ordering::SeqCst); let status = controller.get_status(is_running); let mut tooltip_lines = vec![format!("BandController v{}", version)]; if status.is_running { tooltip_lines.push("监控状态: 运行中".to_string()); tooltip_lines.push(format!("当前速度: {:.2} Mbps", status.current_speed_mbps)); tooltip_lines.push(format!( "已用流量: {}", format_bytes(status.bytes_consumed) )); tooltip_lines.push(format!( "运行时长: {}", format_duration(status.duration_seconds) )); tooltip_lines.push(format!("占用等级: {}", level_to_str(&status.level))); } else { tooltip_lines.push("监控状态: 已停止".to_string()); } let tooltip = tooltip_lines.join("\n"); if let Err(e) = tray_icon_clone.set_tooltip(Some(tooltip)) { eprintln!("Failed to set tray tooltip: {}", e); } // Sync autostart menu item state if let Ok(is_enabled) = app_handle.autolaunch().is_enabled() { let is_checked = autostart_for_sync.is_checked().unwrap(); // println!("[Tray Sync] System autostart: {}, Menu item checked: {}", is_enabled, is_checked); if is_checked != is_enabled { // println!("[Tray Sync] State mismatch! Updating menu item to: {}", is_enabled); autostart_for_sync.set_checked(is_enabled).unwrap(); } } tokio::time::sleep(Duration::from_secs(2)).await; } }); Ok(tray_icon) }