diff --git a/Cargo.lock b/Cargo.lock index f22764d..daefa75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1177,6 +1177,7 @@ dependencies = [ "axum", "cursor-server", "libc", + "mimalloc", "rfd", "serde", "serde_json", @@ -3417,6 +3418,15 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", +] + [[package]] name = "libredox" version = "0.1.19" @@ -3604,6 +3614,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mime" version = "0.3.17" diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 1cd30c3..3669495 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -15,6 +15,7 @@ tauri-build = { version = "2", features = [] } axum = "0.8" cursor-server = { path = "../../../server" } libc = "0.2" +mimalloc = "0.1" rfd = "0.15" serde = { version = "1", features = ["derive"] } serde_json = "1" 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/lib.rs b/apps/desktop/src-tauri/src/lib.rs index cef6375..1bbe442 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -6,6 +6,11 @@ mod startup; mod tray; mod update; +// mimalloc 在释放时主动向操作系统归还内存,避免 glibc 保留页导致 +// 关闭窗口后 RSS 无法回落到静默启动水平。 +#[global_allocator] +static GLOBAL_ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc; + pub fn run() -> std::process::ExitCode { if let Some(exit_code) = update::run_replacement_if_requested() { return exit_code; 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(); - } -}