213 lines
8.2 KiB
Rust
213 lines
8.2 KiB
Rust
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<TrayIcon, tauri::Error> {
|
|
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::<AppState>();
|
|
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)
|
|
} |