diff --git a/Cargo.lock b/Cargo.lock index a5e8bd88f..dc0f22749 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1285,9 +1285,7 @@ version = "0.1.0" dependencies = [ "clash-verge-logging", "log", - "tauri", "tokio", - "windows-sys 0.61.2", ] [[package]] diff --git a/Changelog.md b/Changelog.md index 88b8877e7..51f3b6c56 100644 --- a/Changelog.md +++ b/Changelog.md @@ -61,7 +61,7 @@ - 优化应用重启/退出时的资源清理性能, 大幅缩短执行时间 - 优化 WebSocket 连接机制 - 改进旧版 Service 需要重新安装检测流程 -- 优化 macOS 和 Linux 信号处理 +- 优化 macOS, Linux 和 Windows 信号处理 diff --git a/crates/clash-verge-signal/Cargo.toml b/crates/clash-verge-signal/Cargo.toml index 6c59548c8..359fe559b 100644 --- a/crates/clash-verge-signal/Cargo.toml +++ b/crates/clash-verge-signal/Cargo.toml @@ -9,15 +9,5 @@ clash-verge-logging = { workspace = true } log = { workspace = true } tokio = { workspace = true } -[target.'cfg(windows)'.dependencies] -tauri = { workspace = true } -windows-sys = { version = "0.61.2", features = [ - "Win32_Foundation", - "Win32_Graphics_Gdi", - "Win32_System_SystemServices", - "Win32_UI_WindowsAndMessaging", -] } - - [lints] workspace = true diff --git a/crates/clash-verge-signal/src/lib.rs b/crates/clash-verge-signal/src/lib.rs index 3d1d76b86..46b559d33 100644 --- a/crates/clash-verge-signal/src/lib.rs +++ b/crates/clash-verge-signal/src/lib.rs @@ -9,7 +9,7 @@ mod windows; pub(crate) static RUNTIME: OnceLock> = OnceLock::new(); -pub fn register(#[cfg(windows)] app_handle: &tauri::AppHandle, f: F) +pub fn register(f: F) where F: Fn() -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, @@ -31,5 +31,5 @@ where unix::register(f); #[cfg(windows)] - windows::register(app_handle, f); + windows::register(f); } diff --git a/crates/clash-verge-signal/src/unix.rs b/crates/clash-verge-signal/src/unix.rs index 7991e8f74..aa81393bb 100644 --- a/crates/clash-verge-signal/src/unix.rs +++ b/crates/clash-verge-signal/src/unix.rs @@ -1,8 +1,12 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + use clash_verge_logging::{Type, logging}; use tokio::signal::unix::{SignalKind, signal}; use crate::RUNTIME; +static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false); + pub fn register(f: F) where F: Fn() -> Fut + Send + Sync + 'static, @@ -64,6 +68,17 @@ where } } + if IS_CLEANING_UP.load(Ordering::SeqCst) { + logging!( + info, + Type::SystemSignal, + "Already shutting down, ignoring repeated signal: {}", + signal_name + ); + continue; + } + IS_CLEANING_UP.store(true, Ordering::SeqCst); + logging!(info, Type::SystemSignal, "Caught signal {}", signal_name); f().await; diff --git a/crates/clash-verge-signal/src/windows.rs b/crates/clash-verge-signal/src/windows.rs index 4fa682dd3..cf64912c8 100644 --- a/crates/clash-verge-signal/src/windows.rs +++ b/crates/clash-verge-signal/src/windows.rs @@ -1,170 +1,114 @@ -use std::{future::Future, pin::Pin, sync::OnceLock}; - -use tauri::{AppHandle, Manager as _}; -use windows_sys::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::WindowsAndMessaging::{ - CW_USEDEFAULT, CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassW, - WM_ENDSESSION, WM_QUERYENDSESSION, WNDCLASSW, WS_EX_LAYERED, WS_EX_NOACTIVATE, - WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, - }, -}; +use std::sync::atomic::{AtomicBool, Ordering}; use clash_verge_logging::{Type, logging}; +use tokio::signal::windows; 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) +static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false); -type ShutdownHandler = - Box Pin + Send>> + Send + Sync>; - -static SHUTDOWN_HANDLER: OnceLock = OnceLock::new(); - -struct ShutdownState { - hwnd: HWND, -} - -unsafe impl Send for ShutdownState {} -unsafe impl Sync for ShutdownState {} - -impl Drop for ShutdownState { - fn drop(&mut self) { - // this log not be printed, I don't know why. - logging!(info, Type::SystemSignal, "正在销毁系统关闭监听窗口"); - unsafe { - DestroyWindow(self.hwnd); - } - } -} - -unsafe extern "system" fn shutdown_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // refer: https://learn.microsoft.com/zh-cn/windows/win32/shutdown/shutting-down#shutdown-notifications - // only perform reset operations in `WM_ENDSESSION` - match msg { - WM_QUERYENDSESSION => { - logging!( - info, - Type::SystemSignal, - "System is shutting down or user is logging off." - ); - } - WM_ENDSESSION => { - if let Some(handler) = SHUTDOWN_HANDLER.get() { - if let Some(Some(rt)) = RUNTIME.get() { - rt.block_on(async { - logging!( - info, - Type::SystemSignal, - "Session ended, system shutting down." - ); - handler().await; - logging!(info, Type::SystemSignal, "resolved reset finished"); - }); - } else { - logging!( - error, - Type::SystemSignal, - "handle shutdown signal failed, RUNTIME is not available" - ); - } - } else { - logging!( - error, - Type::SystemSignal, - "WM_ENDSESSION received but no shutdown handler is registered" - ); - } - } - _ => {} - }; - unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } -} - -fn encode_wide>(string: S) -> Vec { - std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref()) - .chain(std::iter::once(0)) - .collect::>() -} - -fn get_instance_handle() -> windows_sys::Win32::Foundation::HMODULE { - // Gets the instance handle by taking the address of the - // pseudo-variable created by the microsoft linker: - // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 - - // This is preferred over GetModuleHandle(NULL) because it also works in DLLs: - // https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance - - unsafe extern "C" { - static __ImageBase: windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER; - } - - unsafe { &__ImageBase as *const _ as _ } -} - -//? 我们有机会采用类似 tokio 信号,不阻塞信号线程吗? -pub fn register(app_handle: &AppHandle, f: F) +pub fn register(f: F) where F: Fn() -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, { - let _ = SHUTDOWN_HANDLER.set(Box::new(move || { - let fut = (f)(); - Box::pin(async move { - fut.await; - }) as Pin + Send>> - })); + if let Some(Some(rt)) = RUNTIME.get() { + rt.spawn(async move { + let mut ctrl_c = match windows::ctrl_c() { + Ok(s) => s, + Err(e) => { + logging!( + error, + Type::SystemSignal, + "Failed to register Ctrl+C: {}", + e + ); + return; + } + }; - let class_name = encode_wide("global_shutdown_app"); - unsafe { - let hinstance = get_instance_handle(); + let mut ctrl_close = match windows::ctrl_close() { + Ok(s) => s, + Err(e) => { + logging!( + error, + Type::SystemSignal, + "Failed to register Ctrl+Close: {}", + e + ); + return; + } + }; - let wnd_class = WNDCLASSW { - lpfnWndProc: Some(shutdown_proc), - lpszClassName: class_name.as_ptr(), - hInstance: hinstance, - ..std::mem::zeroed() - }; + let mut ctrl_shutdown = match windows::ctrl_shutdown() { + Ok(s) => s, + Err(e) => { + logging!( + error, + Type::SystemSignal, + "Failed to register Ctrl+Shutdown: {}", + e + ); + return; + } + }; - RegisterClassW(&wnd_class); + let mut ctrl_logoff = match windows::ctrl_logoff() { + Ok(s) => s, + Err(e) => { + logging!( + error, + Type::SystemSignal, + "Failed to register Ctrl+Logoff: {}", + e + ); + return; + } + }; - let hwnd = CreateWindowExW( - WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED | - // WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which - // we want to avoid. If you remove this style, this window won't show up in the - // taskbar *initially*, but it can show up at some later point. This can sometimes - // happen on its own after several hours have passed, although this has proven - // difficult to reproduce. Alternatively, it can be manually triggered by killing - // `explorer.exe` and then starting the process back up. - // It is unclear why the bug is triggered by waiting for several hours. - WS_EX_TOOLWINDOW, - class_name.as_ptr(), - std::ptr::null(), - WS_OVERLAPPED, - CW_USEDEFAULT, - 0, - CW_USEDEFAULT, - 0, - std::ptr::null_mut(), - std::ptr::null_mut(), - hinstance, - std::ptr::null_mut(), + loop { + let signal_name; + tokio::select! { + _ = ctrl_c.recv() => { + signal_name = "Ctrl+C"; + } + _ = ctrl_close.recv() => { + signal_name = "Ctrl+Close"; + } + _ = ctrl_shutdown.recv() => { + signal_name = "Ctrl+Shutdown"; + } + _ = ctrl_logoff.recv() => { + signal_name = "Ctrl+Logoff"; + } + } + + if IS_CLEANING_UP.load(Ordering::SeqCst) { + logging!( + info, + Type::SystemSignal, + "Already shutting down, ignoring repeated signal: {}", + signal_name + ); + continue; + } + IS_CLEANING_UP.store(true, Ordering::SeqCst); + + logging!( + info, + Type::SystemSignal, + "Caught Windows signal: {}", + signal_name + ); + + f().await; + } + }); + } else { + logging!( + error, + Type::SystemSignal, + "register shutdown signal failed, RUNTIME is not available" ); - if hwnd.is_null() { - logging!( - error, - Type::SystemSignal, - "failed to create shutdown window" - ); - } else { - app_handle.manage(ShutdownState { hwnd }); - } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 646657be5..eb27f8116 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -405,21 +405,20 @@ pub fn run() { event_handlers::handle_reopen(has_visible_windows).await; }); } - #[cfg(target_os = "macos")] tauri::RunEvent::Exit => AsyncHandler::block_on(async { if !handle::Handle::global().is_exiting() { feat::quit().await; } }), tauri::RunEvent::ExitRequested { api, code, .. } => { - AsyncHandler::block_on(async { - let _ = handle::Handle::mihomo().await.clear_all_ws_connections().await; - }); - if core::handle::Handle::global().is_exiting() { return; } + AsyncHandler::block_on(async { + let _ = handle::Handle::mihomo().await.clear_all_ws_connections().await; + }); + if code.is_none() { api.prevent_exit(); } diff --git a/src-tauri/src/utils/resolve/mod.rs b/src-tauri/src/utils/resolve/mod.rs index 01c2aa0c6..a1a1c8fe3 100644 --- a/src-tauri/src/utils/resolve/mod.rs +++ b/src-tauri/src/utils/resolve/mod.rs @@ -143,11 +143,7 @@ pub(super) async fn init_auto_backup() { pub fn init_signal() { logging!(info, Type::Setup, "Initializing signal handlers..."); - clash_verge_signal::register( - #[cfg(windows)] - handle::Handle::app_handle(), - feat::quit, - ); + clash_verge_signal::register(feat::quit); } pub async fn init_work_config() {