diff --git a/src-tauri/src/core/auto_backup.rs b/src-tauri/src/core/auto_backup.rs index c475cb711..a3048bab6 100644 --- a/src-tauri/src/core/auto_backup.rs +++ b/src-tauri/src/core/auto_backup.rs @@ -7,11 +7,10 @@ use crate::{ use anyhow::Result; use chrono::Local; use clash_verge_logging::{Type, logging}; -use parking_lot::RwLock; use std::{ path::PathBuf, sync::{ - Arc, OnceLock, + OnceLock, atomic::{AtomicBool, AtomicI64, Ordering}, }, time::{Duration, UNIX_EPOCH}, @@ -82,8 +81,9 @@ impl Default for AutoBackupSettings { } pub struct AutoBackupManager { - settings: Arc>, settings_tx: watch::Sender, + // The watch channel is the single settings store; no duplicate copies. + settings_rx: watch::Receiver, runner_started: AtomicBool, exec_lock: Mutex<()>, last_backup: AtomicI64, @@ -93,10 +93,10 @@ impl AutoBackupManager { pub fn global() -> &'static Self { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { - let (tx, _rx) = watch::channel(AutoBackupSettings::default()); + let (settings_tx, settings_rx) = watch::channel(AutoBackupSettings::default()); Self { - settings: Arc::new(RwLock::new(AutoBackupSettings::default())), - settings_tx: tx, + settings_tx, + settings_rx, runner_started: AtomicBool::new(false), exec_lock: Mutex::new(()), last_backup: AtomicI64::new(0), @@ -106,9 +106,6 @@ impl AutoBackupManager { pub async fn init(&self) -> Result<()> { let settings = Self::load_settings().await; - { - *self.settings.write() = settings; - } let _ = self.settings_tx.send(settings); self.maybe_start_runner(settings); Ok(()) @@ -116,9 +113,6 @@ impl AutoBackupManager { pub async fn refresh_settings(&self) -> Result<()> { let settings = Self::load_settings().await; - { - *self.settings.write() = settings; - } let _ = self.settings_tx.send(settings); self.maybe_start_runner(settings); Ok(()) @@ -193,7 +187,7 @@ impl AutoBackupManager { } async fn execute_trigger(&self, trigger: AutoBackupTrigger) -> Result<()> { - let snapshot = *self.settings.read(); + let snapshot = *self.settings_rx.borrow(); if trigger.is_schedule() && !snapshot.schedule_enabled { return Ok(()); diff --git a/src-tauri/src/core/lightweight.rs b/src-tauri/src/core/lightweight.rs index 9b4cb0041..fd1e4d363 100644 --- a/src-tauri/src/core/lightweight.rs +++ b/src-tauri/src/core/lightweight.rs @@ -40,8 +40,47 @@ impl LightweightState { static LIGHTWEIGHT_STATE: AtomicU8 = AtomicU8::new(LightweightState::Normal as u8); -static WINDOW_CLOSE_HANDLER_ID: AtomicU32 = AtomicU32::new(0); -static WEBVIEW_FOCUS_HANDLER_ID: AtomicU32 = AtomicU32::new(0); +struct WindowListener { + handler_id: AtomicU32, + event: &'static str, + label: &'static str, +} + +impl WindowListener { + fn setup(&self, on_event: impl Fn() + Send + 'static) { + if let Some(window) = WindowManager::get_main_window() { + let previous_handler_id = self.handler_id.swap(0, Ordering::AcqRel); + if previous_handler_id != 0 { + window.unlisten(previous_handler_id); + logging!(debug, Type::Lightweight, "覆盖旧的{}监听", self.label); + } + let handler_id = window.listen(self.event, move |_event| on_event()); + self.handler_id.store(handler_id, Ordering::Release); + } + } + + fn cancel(&self) { + let id = self.handler_id.swap(0, Ordering::AcqRel); + if id != 0 { + if let Some(window) = WindowManager::get_main_window() { + window.unlisten(id); + } + logging!(debug, Type::Lightweight, "取消了{}监听", self.label); + } + } +} + +static WINDOW_CLOSE_LISTENER: WindowListener = WindowListener { + handler_id: AtomicU32::new(0), + event: "tauri://close-requested", + label: "窗口关闭", +}; + +static WEBVIEW_FOCUS_LISTENER: WindowListener = WindowListener { + handler_id: AtomicU32::new(0), + event: "tauri://focus", + label: "窗口焦点", +}; static CANCEL_TX: Mutex>> = Mutex::new(None); @@ -154,55 +193,27 @@ pub async fn add_light_weight_timer() { } fn setup_window_close_listener() { - if let Some(window) = WindowManager::get_main_window() { - let previous_handler_id = WINDOW_CLOSE_HANDLER_ID.swap(0, Ordering::AcqRel); - if previous_handler_id != 0 { - window.unlisten(previous_handler_id); - logging!(debug, Type::Lightweight, "覆盖旧的窗口关闭监听"); - } - let handler_id = window.listen("tauri://close-requested", move |_event| { - std::mem::drop(AsyncHandler::spawn(|| async { - setup_light_weight_timer().await; - })); - logging!(info, Type::Lightweight, "监听到关闭请求,开始轻量模式计时"); - }); - WINDOW_CLOSE_HANDLER_ID.store(handler_id, Ordering::Release); - } + WINDOW_CLOSE_LISTENER.setup(|| { + std::mem::drop(AsyncHandler::spawn(|| async { + setup_light_weight_timer().await; + })); + logging!(info, Type::Lightweight, "监听到关闭请求,开始轻量模式计时"); + }); } fn cancel_window_close_listener() { - let id = WINDOW_CLOSE_HANDLER_ID.swap(0, Ordering::AcqRel); - if id != 0 { - if let Some(window) = WindowManager::get_main_window() { - window.unlisten(id); - } - logging!(debug, Type::Lightweight, "取消了窗口关闭监听"); - } + WINDOW_CLOSE_LISTENER.cancel(); } fn setup_webview_focus_listener() { - if let Some(window) = WindowManager::get_main_window() { - let previous_handler_id = WEBVIEW_FOCUS_HANDLER_ID.swap(0, Ordering::AcqRel); - if previous_handler_id != 0 { - window.unlisten(previous_handler_id); - logging!(debug, Type::Lightweight, "覆盖旧的窗口焦点监听"); - } - let handler_id = window.listen("tauri://focus", move |_event| { - cancel_light_weight_timer(); - logging!(debug, Type::Lightweight, "监听到窗口获得焦点,取消轻量模式计时"); - }); - WEBVIEW_FOCUS_HANDLER_ID.store(handler_id, Ordering::Release); - } + WEBVIEW_FOCUS_LISTENER.setup(|| { + cancel_light_weight_timer(); + logging!(debug, Type::Lightweight, "监听到窗口获得焦点,取消轻量模式计时"); + }); } fn cancel_webview_focus_listener() { - let id = WEBVIEW_FOCUS_HANDLER_ID.swap(0, Ordering::AcqRel); - if id != 0 { - if let Some(window) = WindowManager::get_main_window() { - window.unlisten(id); - } - logging!(debug, Type::Lightweight, "取消了窗口焦点监听"); - } + WEBVIEW_FOCUS_LISTENER.cancel(); } async fn setup_light_weight_timer() { diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 7d9242087..5733a24aa 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -593,50 +593,71 @@ fn packaged_service_tool_path(file_name: &str, packaged_path: impl FnOnce() -> R } #[cfg(target_os = "windows")] -fn uninstall_service() -> Result<()> { - logging!(info, Type::Service, "uninstall service"); - +pub(super) fn run_maybe_elevated(program: &Path, hide_window: bool) -> Result { use deelevate::{PrivilegeLevel, Token}; use runas::Command as RunasCommand; use std::os::windows::process::CommandExt as _; - let uninstall_path = packaged_service_tool_path("clash-verge-service-uninstall.exe", || { - Ok(dirs::service_path()?.with_file_name("clash-verge-service-uninstall.exe")) - })?; - - if !uninstall_path.exists() { - bail!(format!("uninstaller not found: {uninstall_path:?}")); - } - let token = Token::with_current_process()?; let level = token.privilege_level()?; let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?, - _ => StdCommand::new(uninstall_path).creation_flags(0x08000000).status()?, + PrivilegeLevel::NotPrivileged => { + let mut command = RunasCommand::new(program); + if hide_window { + command.show(false); + } + command.status()? + } + _ => { + let mut command = StdCommand::new(program); + if hide_window { + // 0x08000000 is CREATE_NO_WINDOW, the direct counterpart of runas's show(false). + command.creation_flags(0x08000000); + } + command.status()? + } }; + Ok(status) +} +fn uninstaller_tool_path(file_name: &str, packaged_path: impl FnOnce() -> Result) -> Result { + let uninstall_path = packaged_service_tool_path(file_name, packaged_path)?; + if !uninstall_path.exists() { + bail!(format!("uninstaller not found: {uninstall_path:?}")); + } + Ok(uninstall_path) +} + +fn ensure_uninstall_success(status: &std::process::ExitStatus) -> Result<()> { if !status.success() { bail!( "failed to uninstall service with status {}", status.code().unwrap_or(-1) ); } - Ok(()) } +#[cfg(target_os = "windows")] +fn uninstall_service() -> Result<()> { + logging!(info, Type::Service, "uninstall service"); + + let uninstall_path = uninstaller_tool_path("clash-verge-service-uninstall.exe", || { + Ok(dirs::service_path()?.with_file_name("clash-verge-service-uninstall.exe")) + })?; + + let status = run_maybe_elevated(&uninstall_path, true)?; + ensure_uninstall_success(&status) +} + #[cfg(target_os = "linux")] fn uninstall_service() -> Result<()> { logging!(info, Type::Service, "uninstall service"); - let uninstall_path = packaged_service_tool_path("clash-verge-service-uninstall", || { + let uninstall_path = uninstaller_tool_path("clash-verge-service-uninstall", || { Ok(tauri::utils::platform::current_exe()?.with_file_name("clash-verge-service-uninstall")) })?; - if !uninstall_path.exists() { - bail!(format!("uninstaller not found: {uninstall_path:?}")); - } - let elevator = crate::utils::help::linux_elevator(); let status = if linux_running_as_root() { StdCommand::new(&uninstall_path).status()? @@ -666,14 +687,7 @@ fn uninstall_service() -> Result<()> { status.code().unwrap_or(-1) ); - if !status.success() { - bail!( - "failed to uninstall service with status {}", - status.code().unwrap_or(-1) - ); - } - - Ok(()) + ensure_uninstall_success(&status) } #[cfg(target_os = "linux")] @@ -688,14 +702,10 @@ fn linux_running_as_root() -> bool { fn uninstall_service() -> Result<()> { logging!(info, Type::Service, "uninstall service"); - let uninstall_path = packaged_service_tool_path("clash-verge-service-uninstall", || { + let uninstall_path = uninstaller_tool_path("clash-verge-service-uninstall", || { Ok(dirs::service_path()?.with_file_name("clash-verge-service-uninstall")) })?; - if !uninstall_path.exists() { - bail!(format!("uninstaller not found: {uninstall_path:?}")); - } - let uninstall_path = macos_service_tool_path(&uninstall_path)?; let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned(); @@ -711,14 +721,7 @@ fn uninstall_service() -> Result<()> { let status = StdCommand::new("osascript").args(vec!["-e", &command]).status()?; - if !status.success() { - bail!( - "failed to uninstall service with status {}", - status.code().unwrap_or(-1) - ); - } - - Ok(()) + ensure_uninstall_success(&status) } fn install_service() -> Result<()> { diff --git a/src-tauri/src/core/win_uwp.rs b/src-tauri/src/core/win_uwp.rs index 3db9a1a44..47a186a0a 100644 --- a/src-tauri/src/core/win_uwp.rs +++ b/src-tauri/src/core/win_uwp.rs @@ -1,10 +1,7 @@ #![cfg(target_os = "windows")] -use crate::utils::dirs; +use crate::{core::service, utils::dirs}; use anyhow::{Result, bail}; -use deelevate::{PrivilegeLevel, Token}; -use runas::Command as RunasCommand; -use std::process::Command as StdCommand; pub fn invoke_uwptools() -> Result<()> { let resource_dir = dirs::app_resources_dir()?; @@ -14,13 +11,7 @@ pub fn invoke_uwptools() -> Result<()> { bail!("enableLoopback exe not found"); } - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new(tool_path).status()?, - _ => StdCommand::new(tool_path).status()?, - }; + service::run_maybe_elevated(&tool_path, false)?; Ok(()) }