diff --git a/call-client/package.json b/call-client/package.json index a7d45be..f07d421 100644 --- a/call-client/package.json +++ b/call-client/package.json @@ -1,7 +1,7 @@ { "name": "call-client", "private": true, - "version": "0.1.2", + "version": "0.1.3", "type": "module", "scripts": { "dev": "vite", diff --git a/call-client/src-tauri/Cargo.lock b/call-client/src-tauri/Cargo.lock index 7eff3b8..2cff125 100644 --- a/call-client/src-tauri/Cargo.lock +++ b/call-client/src-tauri/Cargo.lock @@ -392,7 +392,7 @@ dependencies = [ [[package]] name = "call-client" -version = "0.1.2" +version = "0.1.3" dependencies = [ "chrono", "fs2", @@ -2173,12 +2173,46 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "libappindicator" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + [[package]] name = "libc" version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libredox" version = "0.1.15" @@ -4276,6 +4310,7 @@ dependencies = [ "core-foundation 0.9.4", "core-graphics", "crossbeam-channel", + "dirs-next", "dispatch", "gdk", "gdk-pixbuf", @@ -4290,6 +4325,7 @@ dependencies = [ "instant", "jni", "lazy_static", + "libappindicator", "libc", "log", "ndk", diff --git a/call-client/src-tauri/Cargo.toml b/call-client/src-tauri/Cargo.toml index 99b3d7c..24e6304 100644 --- a/call-client/src-tauri/Cargo.toml +++ b/call-client/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "call-client" -version = "0.1.2" +version = "0.1.3" description = "A Tauri App" authors = ["you"] edition = "2021" @@ -22,7 +22,7 @@ default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] [dependencies] -tauri = { version = "1", features = ["api-all"] } +tauri = { version = "1", features = ["api-all", "system-tray"] } serde = { version = "1", features = ["derive"] } serde_json = "1" fs2 = "0.4" diff --git a/call-client/src-tauri/src/commands/window.rs b/call-client/src-tauri/src/commands/window.rs index 8b23fa9..418adbf 100644 --- a/call-client/src-tauri/src/commands/window.rs +++ b/call-client/src-tauri/src/commands/window.rs @@ -1,8 +1,61 @@ -use tauri::{AppHandle, Manager, WindowBuilder, WindowUrl}; +use tauri::{AppHandle, Manager, PhysicalPosition, Window, WindowBuilder, WindowUrl}; use tauri::State; use crate::{commands::logger::app_log, commands::sync::cleanup_screen_sync, state::AppState}; +/// 托盘「叫号窗口」:`main` 已显示且未最小化则最小化;否则显示并置于主屏中央再聚焦。 +pub fn toggle_main_call_window_from_tray(app: &AppHandle) { + if ensure_main_window(app.clone()).is_err() { + return; + } + let Some(main) = app.get_window("main") else { + return; + }; + + let visible = main.is_visible().unwrap_or(false); + let minimized = main.is_minimized().unwrap_or(false); + + if minimized { + let _ = main.unminimize(); + let _ = main.show(); + let _ = center_window_on_primary_monitor(&main); + let _ = main.set_focus(); + return; + } + + if visible { + let _ = main.minimize(); + return; + } + + let _ = main.show(); + let _ = center_window_on_primary_monitor(&main); + let _ = main.set_focus(); +} + +/// 将窗口置于主显示器工作区大致中央(无可用 monitor 信息时回退 `center()`)。 +fn center_window_on_primary_monitor(window: &Window) { + let Ok(Some(monitor)) = window.primary_monitor() else { + let _ = window.center(); + return; + }; + + let mon_pos = monitor.position(); + let mon_size = monitor.size(); + let Ok(outer) = window.outer_size() else { + let _ = window.center(); + return; + }; + + let w = outer.width as i32; + let h = outer.height as i32; + let screen_w = mon_size.width as i32; + let screen_h = mon_size.height as i32; + let x = mon_pos.x + (screen_w - w) / 2; + let y = mon_pos.y + (screen_h - h) / 2; + let _ = window.set_position(PhysicalPosition::new(x, y)); +} + pub fn ensure_main_window(app: AppHandle) -> Result<(), String> { if app.get_window("main").is_some() { return Ok(()); diff --git a/call-client/src-tauri/src/lib.rs b/call-client/src-tauri/src/lib.rs index abc4f25..9944253 100644 --- a/call-client/src-tauri/src/lib.rs +++ b/call-client/src-tauri/src/lib.rs @@ -11,16 +11,23 @@ use commands::{ events::{emit_to_window, list_windows}, logger::{app_log, get_log_paths}, session::{session_clear, session_get, session_set}, - sync::{start_screen_sync, stop_screen_sync}, + sync::{cleanup_screen_sync, start_screen_sync, stop_screen_sync}, update::{check_apt_update, setup_zyyun_apt_source, upgrade_call_client_via_apt}, window::{ close_taxer_info_window, close_ticket_window, ensure_main_window, focus_window, open_login_window, open_main_window, open_taxer_info_window, open_ticket_window, quit_app, + toggle_main_call_window_from_tray, }, }; use fs2::FileExt; use state::AppState; -use tauri::Manager; +use tauri::{ + CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, +}; + +const TRAY_MENU_SHOW_ID: &str = "tray_show_window"; +const TRAY_MENU_TICKETS_ID: &str = "tray_open_ticket_list"; +const TRAY_MENU_QUIT_ID: &str = "tray_quit"; #[allow(dead_code)] struct SingleInstanceLock(File); @@ -48,7 +55,37 @@ fn acquire_single_instance_lock(app: &tauri::App) -> Result { + toggle_main_call_window_from_tray(app); + } + SystemTrayEvent::MenuItemClick { id, .. } if id.as_str() == TRAY_MENU_SHOW_ID => { + toggle_main_call_window_from_tray(app); + } + SystemTrayEvent::MenuItemClick { id, .. } if id.as_str() == TRAY_MENU_TICKETS_ID => { + let _ = open_ticket_window(app.clone()); + } + SystemTrayEvent::MenuItemClick { id, .. } if id.as_str() == TRAY_MENU_QUIT_ID => { + let state = app.state::(); + cleanup_screen_sync(&state); + app.exit(0); + } + _ => {} + }) .manage(AppState::default()) .setup(|app| { match acquire_single_instance_lock(app) { diff --git a/call-client/src-tauri/tauri.conf.json b/call-client/src-tauri/tauri.conf.json index 1282e2d..501e3f5 100644 --- a/call-client/src-tauri/tauri.conf.json +++ b/call-client/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/schema.json", "package": { "productName": "call-client", - "version": "0.1.2" + "version": "0.1.3" }, "build": { "beforeDevCommand": "npm run dev", @@ -55,6 +55,9 @@ "alwaysOnTop": true } ], + "systemTray": { + "iconPath": "icons/icon.ico" + }, "security": { "csp": null },