mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 16:30:52 +08:00
feat: add signal handling for graceful shutdown on Windows and Unix (#5023)
* feat: add signal handling for graceful shutdown on Windows and Unix Co-authored-by: oomeow <oomeow@outlook.com> * chore: update Cargo.lock * fix(windows): restore shutdown hook build by enabling missing Win32 APIs and removing stray tracing call Includes the required windows-sys feature expansions and replaces a leftover tracing reference so the Windows shutdown hook builds successfully. * fix: add deprecation warnings for encrypt_data and decrypt_data functions --------- Co-authored-by: oomeow <oomeow@outlook.com> Co-authored-by: Slinetrac <realakayuki@gmail.com>
This commit is contained in:
509
src-tauri/Cargo.lock
generated
509
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -112,10 +112,19 @@ winapi = { version = "0.3.9", features = [
|
||||
"winhttp",
|
||||
"winreg",
|
||||
] }
|
||||
windows-sys = { version = "0.61.2", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
users = "0.11.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
signal-hook = "0.3.18"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2.5.0"
|
||||
tauri-plugin-global-shortcut = "2.3.0"
|
||||
|
||||
@@ -9,6 +9,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
const NONCE_LENGTH: usize = 12;
|
||||
|
||||
/// Encrypt data
|
||||
#[allow(deprecated)]
|
||||
pub fn encrypt_data(data: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let encryption_key = get_encryption_key()?;
|
||||
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
|
||||
@@ -30,6 +31,7 @@ pub fn encrypt_data(data: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
/// Decrypt data
|
||||
#[allow(deprecated)]
|
||||
pub fn decrypt_data(encrypted: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let encryption_key = get_encryption_key()?;
|
||||
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
|
||||
|
||||
@@ -38,7 +38,7 @@ pub async fn quit() {
|
||||
app_handle.exit(if cleanup_result { 0 } else { 1 });
|
||||
}
|
||||
|
||||
async fn clean_async() -> bool {
|
||||
pub async fn clean_async() -> bool {
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
logging!(info, Type::System, "开始执行异步清理操作...");
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod lightweight;
|
||||
pub mod signal;
|
||||
pub mod sysinfo;
|
||||
|
||||
12
src-tauri/src/module/signal/mod.rs
Normal file
12
src-tauri/src/module/signal/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
pub fn register() {
|
||||
#[cfg(windows)]
|
||||
windows::register();
|
||||
|
||||
#[cfg(unix)]
|
||||
unix::register();
|
||||
}
|
||||
36
src-tauri/src/module/signal/unix.rs
Normal file
36
src-tauri/src/module/signal/unix.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use signal_hook::{
|
||||
consts::{SIGHUP, SIGINT, SIGTERM},
|
||||
iterator::Signals,
|
||||
low_level,
|
||||
};
|
||||
|
||||
use crate::{feat, logging, logging_error, utils::logging::Type};
|
||||
|
||||
pub fn register() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
let signals = [SIGTERM, SIGINT, SIGHUP];
|
||||
match Signals::new(signals) {
|
||||
Ok(mut sigs) => {
|
||||
for signal in &mut sigs {
|
||||
let signal_to_str = |signal: i32| match signal {
|
||||
SIGTERM => "SIGTERM",
|
||||
SIGINT => "SIGINT",
|
||||
SIGHUP => "SIGHUP",
|
||||
_ => "UNKNOWN",
|
||||
};
|
||||
logging!(info, Type::System, "捕获到信号 {}", signal_to_str(signal));
|
||||
feat::clean_async().await;
|
||||
// After printing it, do whatever the signal was supposed to do in the first place
|
||||
logging_error!(
|
||||
Type::System,
|
||||
"信号 {:?} 默认处理失败",
|
||||
low_level::emulate_default_handler(signal)
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::System, "注册信号处理器失败: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
126
src-tauri/src/module/signal/windows.rs
Normal file
126
src-tauri/src/module/signal/windows.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use tauri::Manager;
|
||||
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 crate::{core::handle, feat, logging, utils::logging::Type};
|
||||
|
||||
// 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)
|
||||
|
||||
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::System, "正在销毁系统关闭监听窗口");
|
||||
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::System,
|
||||
"System is shutting down or user is logging off."
|
||||
);
|
||||
}
|
||||
WM_ENDSESSION => {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
logging!(info, Type::System, "Session ended, system shutting down.");
|
||||
feat::clean_async().await;
|
||||
logging!(info, Type::System, "resolved reset finished");
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
|
||||
}
|
||||
|
||||
fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
|
||||
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
|
||||
.chain(std::iter::once(0))
|
||||
.collect::<Vec<u16>>()
|
||||
}
|
||||
|
||||
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 _ }
|
||||
}
|
||||
|
||||
pub fn register() {
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
let class_name = encode_wide("global_shutdown_app");
|
||||
unsafe {
|
||||
let hinstance = get_instance_handle();
|
||||
|
||||
let wnd_class = WNDCLASSW {
|
||||
lpfnWndProc: Some(shutdown_proc),
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
hInstance: hinstance,
|
||||
..std::mem::zeroed()
|
||||
};
|
||||
|
||||
RegisterClassW(&wnd_class);
|
||||
|
||||
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(),
|
||||
);
|
||||
if hwnd.is_null() {
|
||||
logging!(error, Type::System, "failed to create shutdown window");
|
||||
} else {
|
||||
app_handle.manage(ShutdownState { hwnd });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,10 @@ use crate::{
|
||||
tray::Tray,
|
||||
},
|
||||
logging, logging_error,
|
||||
module::lightweight::{auto_lightweight_mode_init, run_once_auto_lightweight},
|
||||
module::{
|
||||
lightweight::{auto_lightweight_mode_init, run_once_auto_lightweight},
|
||||
signal,
|
||||
},
|
||||
process::AsyncHandler,
|
||||
utils::{init, logging::Type, server, window_manager::WindowManager},
|
||||
};
|
||||
@@ -30,6 +33,7 @@ pub fn resolve_setup_sync() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
AsyncHandler::spawn_blocking(init_scheme);
|
||||
AsyncHandler::spawn_blocking(init_embed_server);
|
||||
AsyncHandler::spawn_blocking(init_signal);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,6 +136,11 @@ pub(super) async fn init_auto_lightweight_mode() {
|
||||
logging_error!(Type::Setup, auto_lightweight_mode_init().await);
|
||||
}
|
||||
|
||||
pub(super) fn init_signal() {
|
||||
logging!(info, Type::Setup, "Initializing signal handlers...");
|
||||
signal::register();
|
||||
}
|
||||
|
||||
pub async fn init_work_config() {
|
||||
logging_error!(Type::Setup, init::init_config().await);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user