From dbec16dd8b99b51db5211c2e24348bd8c9e49d30 Mon Sep 17 00:00:00 2001 From: Linanwanttodo Date: Wed, 2 Sep 2026 18:20:20 +0800 Subject: [PATCH] perf(desktop): create the main window lazily on demand The webview used to be built unconditionally at startup and only hidden on close, so the forwarding gateway kept WebKitWebProcess and WebKitNetworkProcess resident even when the UI was never used. Now the window is created only when opened from the tray, closing the window destroys the webview, and all-windows-closed keeps the process alive in the tray while the forwarding server keeps running. --- apps/desktop/src-tauri/src/desktop.rs | 89 +++++++++++++++------------ apps/desktop/src-tauri/src/tray.rs | 18 ++---- 2 files changed, 56 insertions(+), 51 deletions(-) diff --git a/apps/desktop/src-tauri/src/desktop.rs b/apps/desktop/src-tauri/src/desktop.rs index c700bea..eb05110 100644 --- a/apps/desktop/src-tauri/src/desktop.rs +++ b/apps/desktop/src-tauri/src/desktop.rs @@ -36,6 +36,7 @@ struct DesktopRuntime { shutdown: CancellationToken, server: Mutex>>>, exiting: AtomicBool, + server_addr: std::net::SocketAddr, } #[tauri::command] @@ -141,6 +142,21 @@ fn create_main_window( builder.build() } +/// 按需打开主窗口:webview 仅在需要界面时创建,关闭窗口即销毁释放内存。 +pub(crate) fn open_main_window(app: &AppHandle) -> tauri::Result<()> { + if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + return Ok(()); + } + let address = app.state::().server_addr; + let window = create_main_window(app, address)?; + window.show()?; + window.set_focus()?; + Ok(()) +} + pub fn run() -> ExitCode { let diagnostics = match StartupDiagnostics::initialize() { Ok(diagnostics) => diagnostics, @@ -184,7 +200,7 @@ pub fn run() -> ExitCode { ]) .plugin(tauri_plugin_single_instance::init(|app, args, _| { if !args.iter().any(|arg| arg == AUTOSTART_ARG) { - tray::show_main_window(app); + let _ = open_main_window(app); } })) .plugin(tauri_plugin_clipboard_manager::init()) @@ -240,13 +256,12 @@ pub fn run() -> ExitCode { shutdown, server: Mutex::new(Some(task)), exiting: AtomicBool::new(false), + server_addr: address, }); - let window = create_main_window(app.handle(), address)?; if desktop_settings.silent_start && started_by_autostart { - tracing::info!("silent autostart enabled; keeping the main window hidden"); + tracing::info!("silent autostart enabled; starting without the main window"); } else { - window.show()?; - window.set_focus()?; + open_main_window(app.handle())?; } tray::create(app)?; crate::update::signal_ready_if_requested()?; @@ -262,46 +277,42 @@ pub fn run() -> ExitCode { }; app.run(|app, event| match event { - RunEvent::WindowEvent { - label, - event: tauri::WindowEvent::CloseRequested { api, .. }, - .. - } if label == MAIN_WINDOW_LABEL => { - let runtime = app.state::(); - if !runtime.exiting.load(Ordering::Acquire) { - api.prevent_close(); - if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) { - let _ = window.hide(); + // code 为 None 表示所有窗口已被关闭(轻量模式),阻止退出, + // 转发服务继续在托盘后台运行;code 为 Some 时是显式退出请求。 + RunEvent::ExitRequested { code, api, .. } => match code { + None => api.prevent_exit(), + Some(_) => { + let runtime = app.state::(); + if !runtime.exiting.swap(true, Ordering::AcqRel) { + api.prevent_exit(); + runtime.shutdown.cancel(); + let server = runtime.server.lock().expect("server lock poisoned").take(); + let app = app.clone(); + tauri::async_runtime::spawn(async move { + if let Some(server) = server { + match tokio::time::timeout(Duration::from_secs(11), server).await { + Ok(Ok(Ok(()))) => {} + Ok(Ok(Err(error))) => { + tracing::error!(%error, "desktop server shutdown failed") + } + Ok(Err(error)) => { + tracing::error!(%error, "desktop server task failed") + } + Err(_) => tracing::warn!("desktop server shutdown timed out"), + } + } + app.exit(0); + }); } } - } - RunEvent::ExitRequested { api, .. } => { - let runtime = app.state::(); - if !runtime.exiting.swap(true, Ordering::AcqRel) { - api.prevent_exit(); - runtime.shutdown.cancel(); - let server = runtime.server.lock().expect("server lock poisoned").take(); - let app = app.clone(); - tauri::async_runtime::spawn(async move { - if let Some(server) = server { - match tokio::time::timeout(Duration::from_secs(11), server).await { - Ok(Ok(Ok(()))) => {} - Ok(Ok(Err(error))) => { - tracing::error!(%error, "desktop server shutdown failed") - } - Ok(Err(error)) => tracing::error!(%error, "desktop server task failed"), - Err(_) => tracing::warn!("desktop server shutdown timed out"), - } - } - app.exit(0); - }); - } - } + }, #[cfg(target_os = "macos")] RunEvent::Reopen { has_visible_windows: false, .. - } => tray::show_main_window(app), + } => { + let _ = open_main_window(app); + } _ => {} }); diff --git a/apps/desktop/src-tauri/src/tray.rs b/apps/desktop/src-tauri/src/tray.rs index a5a2d77..1e7c5df 100644 --- a/apps/desktop/src-tauri/src/tray.rs +++ b/apps/desktop/src-tauri/src/tray.rs @@ -1,13 +1,13 @@ use tauri::{ menu::{Menu, MenuItem, PredefinedMenuItem}, tray::TrayIconBuilder, - App, AppHandle, Manager, + App, }; #[cfg(target_os = "windows")] use tauri::tray::{MouseButton, MouseButtonState, TrayIconEvent}; -use crate::desktop::MAIN_WINDOW_LABEL; +use crate::desktop::open_main_window; const OPEN_MENU_ID: &str = "tray-open"; const QUIT_MENU_ID: &str = "tray-quit"; @@ -24,7 +24,9 @@ pub fn create(app: &mut App) -> tauri::Result<()> { .menu(&menu) .show_menu_on_left_click(cfg!(target_os = "macos")) .on_menu_event(|app, event| match event.id().as_ref() { - OPEN_MENU_ID => show_main_window(app), + OPEN_MENU_ID => { + let _ = open_main_window(app); + } QUIT_MENU_ID => app.exit(0), _ => {} }) @@ -36,7 +38,7 @@ pub fn create(app: &mut App) -> tauri::Result<()> { .. } = event { - show_main_window(tray.app_handle()); + let _ = open_main_window(tray.app_handle()); } #[cfg(not(target_os = "windows"))] @@ -45,11 +47,3 @@ pub fn create(app: &mut App) -> tauri::Result<()> { .build(app)?; Ok(()) } - -pub fn show_main_window(app: &AppHandle) { - if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) { - let _ = window.unminimize(); - let _ = window.show(); - let _ = window.set_focus(); - } -}