diff --git a/src-tauri/src/commands/lightweight.rs b/src-tauri/src/commands/lightweight.rs new file mode 100644 index 000000000..2380b35e5 --- /dev/null +++ b/src-tauri/src/commands/lightweight.rs @@ -0,0 +1,14 @@ +#[tauri::command] +pub fn enter_lightweight_mode(app: tauri::AppHandle) -> Result<(), String> { + crate::lightweight::enter_lightweight_mode(&app) +} + +#[tauri::command] +pub fn exit_lightweight_mode(app: tauri::AppHandle) -> Result<(), String> { + crate::lightweight::exit_lightweight_mode(&app) +} + +#[tauri::command] +pub fn is_lightweight_mode() -> bool { + crate::lightweight::is_lightweight_mode() +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 9eb7c659b..09bb312b3 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -25,6 +25,7 @@ mod sync_support; mod usage; mod webdav_sync; mod workspace; +mod lightweight; pub use auth::*; pub use config::*; @@ -50,3 +51,4 @@ pub use stream_check::*; pub use usage::*; pub use webdav_sync::*; pub use workspace::*; +pub use lightweight::*; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ef464d687..05463cee5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,6 +12,7 @@ mod error; mod gemini_config; mod gemini_mcp; mod init_status; +mod lightweight; mod mcp; mod openclaw_config; mod opencode_config; @@ -204,6 +205,12 @@ pub fn run() { log::debug!(" arg[{i}]: {}", redact_url_for_log(arg)); } + if crate::lightweight::is_lightweight_mode() { + if let Err(e) = crate::lightweight::exit_lightweight_mode(app) { + log::error!("退出轻量模式重建窗口失败: {e}"); + } + } + // Check for deep link URL in args (mainly for Windows/Linux command line) let mut found_deeplink = false; for arg in &args { @@ -615,6 +622,12 @@ pub fn run() { let urls = event.urls(); log::info!("Received {} URL(s)", urls.len()); + if crate::lightweight::is_lightweight_mode() { + if let Err(e) = crate::lightweight::exit_lightweight_mode(&app_handle) { + log::error!("退出轻量模式重建窗口失败: {e}"); + } + } + for (i, url) in urls.iter().enumerate() { let url_str = url.as_str(); log::debug!(" URL[{i}]: {}", redact_url_for_log(url_str)); @@ -1085,6 +1098,10 @@ pub fn run() { commands::delete_daily_memory_file, commands::search_daily_memory_files, commands::open_workspace_directory, + // lightweight mode (for testing or low-resource environments) + commands::enter_lightweight_mode, + commands::exit_lightweight_mode, + commands::is_lightweight_mode, ]); let app = builder @@ -1135,6 +1152,10 @@ pub fn run() { let _ = window.show(); let _ = window.set_focus(); tray::apply_tray_policy(app_handle, true); + } else if crate::lightweight::is_lightweight_mode() { + if let Err(e) = crate::lightweight::exit_lightweight_mode(app_handle) { + log::error!("退出轻量模式重建窗口失败: {e}"); + } } } // 处理通过自定义 URL 协议触发的打开事件(例如 ccswitch://...) @@ -1144,6 +1165,13 @@ pub fn run() { log::info!("RunEvent::Opened with URL: {url_str}"); if url_str.starts_with("ccswitch://") { + if crate::lightweight::is_lightweight_mode() { + if let Err(e) = crate::lightweight::exit_lightweight_mode(app_handle) + { + log::error!("退出轻量模式重建窗口失败: {e}"); + } + } + // 解析并广播深链接事件,复用与 single_instance 相同的逻辑 match crate::deeplink::parse_deeplink_url(&url_str) { Ok(request) => { diff --git a/src-tauri/src/lightweight.rs b/src-tauri/src/lightweight.rs new file mode 100644 index 000000000..cd0875710 --- /dev/null +++ b/src-tauri/src/lightweight.rs @@ -0,0 +1,90 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use tauri::Manager; + +static LIGHTWEIGHT_MODE: AtomicBool = AtomicBool::new(false); + +pub fn enter_lightweight_mode(app: &tauri::AppHandle) -> Result<(), String> { + #[cfg(target_os = "windows")] + { + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_skip_taskbar(true); + } + } + #[cfg(target_os = "macos")] + { + crate::tray::apply_tray_policy(app, false); + } + + if let Some(window) = app.get_webview_window("main") { + window + .destroy() + .map_err(|e| format!("销毁主窗口失败: {e}"))?; + } + // else: already in lightweight mode or window not found, just set the flag + + LIGHTWEIGHT_MODE.store(true, Ordering::Release); + crate::tray::refresh_tray_menu(app); + log::info!("进入轻量模式"); + Ok(()) +} + +pub fn exit_lightweight_mode(app: &tauri::AppHandle) -> Result<(), String> { + use tauri::WebviewWindowBuilder; + + if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + #[cfg(target_os = "windows")] + { + let _ = window.set_skip_taskbar(false); + } + #[cfg(target_os = "macos")] + { + crate::tray::apply_tray_policy(app, true); + } + LIGHTWEIGHT_MODE.store(false, Ordering::Release); + crate::tray::refresh_tray_menu(app); + log::info!("退出轻量模式"); + return Ok(()); + } + + let window_config = app + .config() + .app + .windows + .iter() + .find(|w| w.label == "main") + .ok_or("主窗口配置未找到")?; + + WebviewWindowBuilder::from_config(app, window_config) + .map_err(|e| format!("加载主窗口配置失败: {e}"))? + .visible(true) + .build() + .map_err(|e| format!("创建主窗口失败: {e}"))?; + + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_focus(); + } + + #[cfg(target_os = "windows")] + { + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_skip_taskbar(false); + } + } + #[cfg(target_os = "macos")] + { + crate::tray::apply_tray_policy(app, true); + } + + LIGHTWEIGHT_MODE.store(false, Ordering::Release); + crate::tray::refresh_tray_menu(app); + log::info!("退出轻量模式"); + Ok(()) +} + +pub fn is_lightweight_mode() -> bool { + LIGHTWEIGHT_MODE.load(Ordering::Acquire) +} \ No newline at end of file diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index e7b991324..f5b068157 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -14,6 +14,7 @@ use crate::store::AppState; pub struct TrayTexts { pub show_main: &'static str, pub no_provider_hint: &'static str, + pub lightweight_mode: &'static str, pub quit: &'static str, pub _auto_label: &'static str, } @@ -24,6 +25,7 @@ impl TrayTexts { "en" => Self { show_main: "Open main window", no_provider_hint: " (No providers yet, please add them from the main window)", + lightweight_mode: "Lightweight Mode", quit: "Quit", _auto_label: "Auto (Failover)", }, @@ -31,12 +33,14 @@ impl TrayTexts { show_main: "メインウィンドウを開く", no_provider_hint: " (プロバイダーがまだありません。メイン画面から追加してください)", + lightweight_mode: "軽量モード", quit: "終了", _auto_label: "自動 (フェイルオーバー)", }, _ => Self { show_main: "打开主界面", no_provider_hint: " (无供应商,请在主界面添加)", + lightweight_mode: "轻量模式", quit: "退出", _auto_label: "自动 (故障转移)", }, @@ -382,6 +386,19 @@ pub fn create_tray_menu( menu_builder = menu_builder.separator(); } + let lightweight_item = CheckMenuItem::with_id( + app, + "lightweight_mode", + tray_texts.lightweight_mode, + true, + crate::lightweight::is_lightweight_mode(), + None::<&str>, + ).map_err(|e| AppError::Message(format!("创建轻量模式菜单失败: {e}")))?; + + menu_builder = menu_builder + .item(&lightweight_item) + .separator(); + // 退出菜单(分隔符已在上面的 section 循环中添加) let quit_item = MenuItem::with_id(app, "quit", tray_texts.quit, true, None::<&str>) .map_err(|e| AppError::Message(format!("创建退出菜单失败: {e}")))?; @@ -393,6 +410,20 @@ pub fn create_tray_menu( .map_err(|e| AppError::Message(format!("构建菜单失败: {e}"))) } +pub fn refresh_tray_menu(app: &tauri::AppHandle) { + use crate::store::AppState; + + if let Some(state) = app.try_state::() { + if let Ok(new_menu) = create_tray_menu(app, state.inner()) { + if let Some(tray) = app.tray_by_id("main") { + if let Err(e) = tray.set_menu(Some(new_menu)) { + log::error!("刷新托盘菜单失败: {e}"); + } + } + } + } +} + #[cfg(target_os = "macos")] pub fn apply_tray_policy(app: &tauri::AppHandle, dock_visible: bool) { use tauri::ActivationPolicy; @@ -430,6 +461,21 @@ pub fn handle_tray_menu_event(app: &tauri::AppHandle, event_id: &str) { { apply_tray_policy(app, true); } + } else if crate::lightweight::is_lightweight_mode() { + if let Err(e) = crate::lightweight::exit_lightweight_mode(app) { + log::error!("退出轻量模式重建窗口失败: {e}"); + } + } + } + "lightweight_mode" => { + if crate::lightweight::is_lightweight_mode() { + if let Err(e) = crate::lightweight::exit_lightweight_mode(app) { + log::error!("退出轻量模式失败: {e}"); + } + } else { + if let Err(e) = crate::lightweight::enter_lightweight_mode(app) { + log::error!("进入轻量模式失败: {e}"); + } } } "quit" => {