diff --git a/Cargo.lock b/Cargo.lock index f1320f97d..287affec2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,6 +1204,7 @@ dependencies = [ "log", "signal-hook 0.3.18", "tauri", + "tokio", "windows-sys 0.61.2", ] @@ -7628,7 +7629,7 @@ dependencies = [ [[package]] name = "tauri-plugin-mihomo" version = "0.1.1" -source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#1c2fba06b1e51eefd14b9f96310e75b9cdbf58fa" +source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#24586eb0721314f88e65460b4ac01933b3376d3c" dependencies = [ "base64 0.22.1", "futures-util", diff --git a/Changelog.md b/Changelog.md index 0a28b4472..a0798b12c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,7 @@ - 仪表盘与托盘状态不同步 - 修复重启或退出应用,关闭系统时无法记忆用户行为 - 彻底修复 macOS 连接页面显示异常 +- windows 端监听关机信号失败
✨ 新增功能 @@ -39,6 +40,7 @@ - 优化托盘菜单当前订阅检测逻辑 - 优化连接页面表格渲染 - 优化链式代理 UI 反馈 +- 优化重启应用的资源清理逻辑
diff --git a/crates/clash-verge-signal/Cargo.toml b/crates/clash-verge-signal/Cargo.toml index 998d9bd24..2781509f2 100644 --- a/crates/clash-verge-signal/Cargo.toml +++ b/crates/clash-verge-signal/Cargo.toml @@ -5,14 +5,15 @@ edition.workspace = true rust-version.workspace = true [dependencies] -tauri = { workspace = true } clash-verge-logging = { workspace = true } log = { workspace = true } +tokio = { workspace = true } [target.'cfg(unix)'.dependencies] signal-hook = "0.3.18" [target.'cfg(windows)'.dependencies] +tauri = { workspace = true } windows-sys = { version = "0.61.2", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", diff --git a/crates/clash-verge-signal/src/lib.rs b/crates/clash-verge-signal/src/lib.rs index c30ad02a5..a7b7671bc 100644 --- a/crates/clash-verge-signal/src/lib.rs +++ b/crates/clash-verge-signal/src/lib.rs @@ -1,13 +1,32 @@ +use std::sync::OnceLock; + +use clash_verge_logging::{Type, logging}; + #[cfg(unix)] mod unix; #[cfg(windows)] mod windows; +pub(crate) static RUNTIME: OnceLock> = OnceLock::new(); + pub fn register(#[cfg(windows)] app_handle: &tauri::AppHandle, f: F) where F: Fn() -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, { + RUNTIME.get_or_init(|| match tokio::runtime::Runtime::new() { + Ok(rt) => Some(rt), + Err(e) => { + logging!( + info, + Type::System, + "register shutdown signal failed, create tokio runtime error: {}", + e + ); + None + } + }); + #[cfg(unix)] unix::register(f); diff --git a/crates/clash-verge-signal/src/unix.rs b/crates/clash-verge-signal/src/unix.rs index 3ec421eeb..9729c32b4 100644 --- a/crates/clash-verge-signal/src/unix.rs +++ b/crates/clash-verge-signal/src/unix.rs @@ -6,39 +6,49 @@ use signal_hook::{ use clash_verge_logging::{Type, logging, logging_error}; +use crate::RUNTIME; + pub fn register(f: F) where F: Fn() -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, { - tauri::async_runtime::spawn(async move { - let signals = [SIGTERM, SIGINT, SIGHUP]; + if let Some(Some(rt)) = RUNTIME.get() { + rt.spawn(async move { + let signals = [SIGTERM, SIGINT, SIGHUP]; - let mut sigs = match Signals::new(signals) { - Ok(s) => s, - Err(e) => { - logging!(error, Type::System, "注册信号处理器失败: {}", e); - return; - } - }; - - for signal in &mut sigs { - let signal_to_str = |signal: i32| match signal { - SIGTERM => "SIGTERM", - SIGINT => "SIGINT", - SIGHUP => "SIGHUP", - _ => "UNKNOWN", + let mut sigs = match Signals::new(signals) { + Ok(s) => s, + Err(e) => { + logging!(error, Type::System, "注册信号处理器失败: {}", e); + return; + } }; - logging!(info, Type::System, "捕获到信号 {}", signal_to_str(signal)); + for signal in &mut sigs { + let signal_to_str = |signal| match signal { + SIGTERM => "SIGTERM", + SIGINT => "SIGINT", + SIGHUP => "SIGHUP", + _ => "UNKNOWN", + }; - f().await; + logging!(info, Type::System, "捕获到信号 {}", signal_to_str(signal)); - logging_error!( - Type::System, - "信号 {:?} 默认处理失败", - low_level::emulate_default_handler(signal) - ); - } - }); + f().await; + + logging_error!( + Type::System, + "信号 {:?} 默认处理失败", + low_level::emulate_default_handler(signal) + ); + } + }); + } else { + logging!( + error, + Type::System, + "register shutdown signal failed, RUNTIME is not available" + ); + } } diff --git a/crates/clash-verge-signal/src/windows.rs b/crates/clash-verge-signal/src/windows.rs index f60d4215a..860208a28 100644 --- a/crates/clash-verge-signal/src/windows.rs +++ b/crates/clash-verge-signal/src/windows.rs @@ -12,6 +12,8 @@ use windows_sys::Win32::{ use clash_verge_logging::{Type, logging}; +use crate::RUNTIME; + // code refer to: // global-hotkey (https://github.com/tauri-apps/global-hotkey) // Global Shortcut (https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/global-shortcut) @@ -56,11 +58,19 @@ unsafe extern "system" fn shutdown_proc( } WM_ENDSESSION => { if let Some(handler) = SHUTDOWN_HANDLER.get() { - tauri::async_runtime::block_on(async { - logging!(info, Type::System, "Session ended, system shutting down."); - handler().await; - logging!(info, Type::System, "resolved reset finished"); - }); + if let Some(Some(rt)) = RUNTIME.get() { + rt.block_on(async { + logging!(info, Type::System, "Session ended, system shutting down."); + handler().await; + logging!(info, Type::System, "resolved reset finished"); + }); + } else { + logging!( + error, + Type::System, + "handle shutdown signal failed, RUNTIME is not available" + ); + } } else { logging!( error, diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index a133ab28d..cee3f49bc 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -1,8 +1,9 @@ use crate::{ config::Config, core::{CoreManager, handle, tray}, + feat::clean_async, process::AsyncHandler, - utils::{self, resolve}, + utils, }; use clash_verge_logging::{Type, logging, logging_error}; use serde_yaml_ng::{Mapping, Value}; @@ -24,16 +25,22 @@ pub async fn restart_clash_core() { /// Restart the application pub async fn restart_app() { + logging!(debug, Type::System, "启动重启应用流程"); utils::server::shutdown_embedded_server(); Config::apply_all_and_save_file().await; - if let Err(err) = resolve::resolve_reset_async().await { - handle::Handle::notice_message( - "restart_app::error", - format!("Failed to cleanup resources: {err}"), - ); - logging!(error, Type::Core, "Restart failed during cleanup: {err}"); - return; - } + + // 设置退出标志 + handle::Handle::global().set_is_exiting(); + + logging!(info, Type::System, "开始异步清理资源"); + let cleanup_result = clean_async().await; + + logging!( + info, + Type::System, + "资源清理完成,退出代码: {}", + if cleanup_result { 0 } else { 1 } + ); let app_handle = handle::Handle::app_handle(); app_handle.restart(); diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index fe1d7848b..5d04f317a 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -23,8 +23,7 @@ pub async fn quit() { utils::server::shutdown_embedded_server(); Config::apply_all_and_save_file().await; - // 获取应用句柄并设置退出标志 - let app_handle = handle::Handle::app_handle(); + // 设置退出标志 handle::Handle::global().set_is_exiting(); logging!(info, Type::System, "开始异步清理资源"); @@ -36,6 +35,8 @@ pub async fn quit() { "资源清理完成,退出代码: {}", if cleanup_result { 0 } else { 1 } ); + + let app_handle = handle::Handle::app_handle(); app_handle.exit(if cleanup_result { 0 } else { 1 }); } @@ -56,6 +57,7 @@ pub async fn clean_async() -> bool { let disable_tun = serde_json::json!({ "tun": { "enable": false } }); + logging!(info, Type::System, "send disable tun request to mihomo"); match timeout( Duration::from_millis(1000), handle::Handle::mihomo() @@ -210,6 +212,7 @@ pub async fn clean_async() -> bool { #[cfg(not(target_os = "windows"))] let stop_timeout = Duration::from_secs(3); + logging!(info, Type::System, "stop core"); match timeout(stop_timeout, CoreManager::global().stop_core()).await { Ok(_) => { logging!(info, Type::Window, "core已停止"); @@ -269,41 +272,6 @@ pub async fn clean_async() -> bool { all_success } -pub fn clean() -> bool { - use crate::process::AsyncHandler; - - let (tx, rx) = std::sync::mpsc::channel(); - - AsyncHandler::spawn(move || async move { - logging!(info, Type::System, "开始执行关闭操作..."); - - // 使用已有的异步清理函数 - let cleanup_result = clean_async().await; - - let _ = tx.send(cleanup_result); - }); - - #[cfg(target_os = "windows")] - let total_timeout = std::time::Duration::from_secs(5); - #[cfg(not(target_os = "windows"))] - let total_timeout = std::time::Duration::from_secs(8); - - match rx.recv_timeout(total_timeout) { - Ok(result) => { - logging!(info, Type::System, "关闭操作完成,结果: {}", result); - result - } - Err(_) => { - logging!( - warn, - Type::System, - "清理操作超时(可能正在关机),返回成功避免阻塞" - ); - true - } - } -} - #[cfg(target_os = "macos")] pub async fn hide() { use crate::module::lightweight::add_light_weight_timer; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 16847b1e0..a6649eab8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,9 +10,10 @@ mod feat; mod module; mod process; pub mod utils; -use crate::constants::files; #[cfg(target_os = "linux")] use crate::utils::linux; +use crate::utils::resolve::init_signal; +use crate::{constants::files, utils::resolve::prioritize_initialization}; use crate::{ core::handle, process::AsyncHandler, @@ -237,13 +238,14 @@ pub fn run() { let builder = app_init::setup_plugins(tauri::Builder::default()) .setup(|app| { - logging!(info, Type::Setup, "开始应用初始化..."); - #[allow(clippy::expect_used)] APP_HANDLE .set(app.app_handle().clone()) .expect("failed to set global app handle"); + let _handle = AsyncHandler::block_on(async { prioritize_initialization().await }); + + logging!(info, Type::Setup, "开始应用初始化..."); if let Err(e) = app_init::setup_autostart(app) { logging!(error, Type::Setup, "Failed to setup autostart: {}", e); } @@ -257,6 +259,7 @@ pub fn run() { resolve::resolve_setup_handle(); resolve::resolve_setup_async(); resolve::resolve_setup_sync(); + init_signal(); logging!(info, Type::Setup, "初始化已启动"); Ok(()) @@ -423,6 +426,10 @@ pub fn run() { event_handlers::handle_reopen(has_visible_windows).await; }); } + #[cfg(target_os = "macos")] + tauri::RunEvent::Exit => AsyncHandler::block_on(async { + feat::quit().await; + }), tauri::RunEvent::ExitRequested { api, code, .. } => { AsyncHandler::block_on(async { let _ = handle::Handle::mihomo() @@ -439,13 +446,6 @@ pub fn run() { api.prevent_exit(); } } - tauri::RunEvent::Exit => { - let handle = core::handle::Handle::global(); - if !handle.is_exiting() { - handle.set_is_exiting(); - feat::clean(); - } - } tauri::RunEvent::WindowEvent { label, event, .. } if label == "main" => match event { tauri::WindowEvent::CloseRequested { .. } => { event_handlers::handle_window_close(&event); diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index d377e9511..eff1a9a58 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -19,7 +19,7 @@ use clash_verge_service_ipc::WriterConfig; use flexi_logger::writers::FileLogWriter; use flexi_logger::{Cleanup, Criterion, FileSpec}; #[cfg(not(feature = "tauri-dev"))] -use flexi_logger::{Duplicate, LogSpecBuilder, Logger}; +use flexi_logger::{Duplicate, LogSpecBuilder, Logger, LoggerHandle}; use std::{path::PathBuf, str::FromStr as _}; use tauri_plugin_shell::ShellExt as _; use tokio::fs; @@ -27,7 +27,7 @@ use tokio::fs::DirEntry; /// initialize this instance's log file #[cfg(not(feature = "tauri-dev"))] -pub async fn init_logger() -> Result<()> { +pub async fn init_logger() -> Result { // TODO 提供 runtime 级别实时修改 let (log_level, log_max_size, log_max_count) = { let verge_guard = Config::verge().await; @@ -47,11 +47,9 @@ pub async fn init_logger() -> Result<()> { .unwrap_or(log_level); spec.default(level); #[cfg(feature = "tracing")] - spec.module("tauri", log::LevelFilter::Debug); - #[cfg(feature = "tracing")] - spec.module("wry", log::LevelFilter::Off); - #[cfg(feature = "tracing")] - spec.module("tauri_plugin_mihomo", log::LevelFilter::Off); + spec.module("tauri", log::LevelFilter::Debug) + .module("wry", log::LevelFilter::Off) + .module("tauri_plugin_mihomo", log::LevelFilter::Off); let spec = spec.build(); let logger = Logger::with(spec) @@ -83,14 +81,14 @@ pub async fn init_logger() -> Result<()> { "kode_bridge", ]))); - let _handle = logger.start()?; + let handle = logger.start()?; // TODO 全局 logger handle 控制 // GlobalLoggerProxy::global().set_inner(handle); // TODO 提供前端设置等级,热更新等级 // logger.parse_new_spec(spec) - Ok(()) + Ok(handle) } pub async fn sidecar_writer() -> Result { diff --git a/src-tauri/src/utils/resolve/mod.rs b/src-tauri/src/utils/resolve/mod.rs index cf32cb434..13fffa7fa 100644 --- a/src-tauri/src/utils/resolve/mod.rs +++ b/src-tauri/src/utils/resolve/mod.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use flexi_logger::LoggerHandle; use crate::{ config::Config, @@ -23,6 +24,21 @@ pub mod ui; pub mod window; pub mod window_script; +pub async fn prioritize_initialization() -> Option { + init_work_config().await; + init_resources().await; + + #[cfg(not(feature = "tauri-dev"))] + { + logging!(info, Type::Setup, "Initializing logger"); + init::init_logger().await.ok() + } + #[cfg(feature = "tauri-dev")] + { + None + } +} + pub fn resolve_setup_handle() { init_handle(); } @@ -31,14 +47,11 @@ pub fn resolve_setup_sync() { AsyncHandler::spawn(|| async { AsyncHandler::spawn_blocking(init_scheme); AsyncHandler::spawn_blocking(init_embed_server); - AsyncHandler::spawn_blocking(init_signal); }); } pub fn resolve_setup_async() { AsyncHandler::spawn(|| async { - #[cfg(not(feature = "tauri-dev"))] - resolve_setup_logger().await; logging!( info, Type::ClashVergeRev, @@ -96,11 +109,6 @@ pub(super) fn init_scheme() { logging_error!(Type::Setup, init::init_scheme()); } -#[cfg(not(feature = "tauri-dev"))] -pub(super) async fn resolve_setup_logger() { - logging_error!(Type::Setup, init::init_logger().await); -} - pub async fn resolve_scheme(param: &str) -> Result<()> { logging_error!(Type::Setup, scheme::resolve_scheme(param).await); Ok(()) @@ -134,7 +142,7 @@ pub(super) async fn init_auto_backup() { logging_error!(Type::Setup, AutoBackupManager::global().init().await); } -pub(super) fn init_signal() { +pub fn init_signal() { logging!(info, Type::Setup, "Initializing signal handlers..."); clash_verge_signal::register( #[cfg(windows)]