From ed08fadb5a787fb02ac4c0db68978a295579e0d3 Mon Sep 17 00:00:00 2001 From: Slinetrac Date: Sun, 2 Nov 2025 23:42:26 +0800 Subject: [PATCH 01/10] fix: refresh_lightweight_tray_state #5285 --- src-tauri/src/module/lightweight.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index d5c3900e..9390639f 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -1,6 +1,6 @@ use crate::{ config::Config, - core::{handle, timer::Timer}, + core::{handle, timer::Timer, tray::Tray}, log_err, logging, process::AsyncHandler, utils::logging::Type, @@ -78,6 +78,12 @@ pub fn is_in_lightweight_mode() -> bool { get_state() == LightweightState::In } +async fn refresh_lightweight_tray_state() { + if let Err(err) = Tray::global().update_tray_display().await { + logging!(warn, Type::Lightweight, "更新托盘轻量模式状态失败: {err}"); + } +} + pub async fn auto_lightweight_boot() -> Result<()> { let verge_config = Config::verge().await; let enable_auto = verge_config @@ -130,11 +136,13 @@ pub fn disable_auto_light_weight_mode() { pub async fn entry_lightweight_mode() -> bool { if !try_transition(LightweightState::Normal, LightweightState::In) { logging!(info, Type::Lightweight, "无需进入轻量模式,跳过调用"); + refresh_lightweight_tray_state().await; return false; } record_state_and_log(LightweightState::In); WindowManager::destroy_main_window(); let _ = cancel_light_weight_timer(); + refresh_lightweight_tray_state().await; true } @@ -145,12 +153,14 @@ pub async fn exit_lightweight_mode() -> bool { Type::Lightweight, "轻量模式不在退出条件(可能已退出或正在退出),跳过调用" ); + refresh_lightweight_tray_state().await; return false; } record_state_and_log(LightweightState::Exiting); WindowManager::show_main_window().await; let _ = cancel_light_weight_timer(); record_state_and_log(LightweightState::Normal); + refresh_lightweight_tray_state().await; true } From dce349586c536dda60015244dffd3c7233cd8651 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 3 Nov 2025 03:17:33 +0800 Subject: [PATCH 02/10] refactor: simplify profile retrieval and remove unused template method --- src-tauri/src/cmd/profile.rs | 55 +++----------------------------- src-tauri/src/config/profiles.rs | 17 +++------- src-tauri/src/feat/backup.rs | 14 +++++--- src-tauri/src/utils/init.rs | 2 +- src/services/types.d.ts | 1 - 5 files changed, 19 insertions(+), 70 deletions(-) diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 949ddce1..aadc6798 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -26,57 +26,10 @@ static CURRENT_SWITCHING_PROFILE: AtomicBool = AtomicBool::new(false); #[tauri::command] pub async fn get_profiles() -> CmdResult { - // 策略1: 尝试快速获取latest数据 - let latest_result = tokio::time::timeout(Duration::from_millis(500), async { - let profiles = Config::profiles().await; - let latest = profiles.latest_ref(); - IProfiles { - current: latest.current.clone(), - items: latest.items.clone(), - } - }) - .await; - - match latest_result { - Ok(profiles) => { - logging!(info, Type::Cmd, "快速获取配置列表成功"); - return Ok(profiles); - } - Err(_) => { - logging!(warn, Type::Cmd, "快速获取配置超时(500ms)"); - } - } - - // 策略2: 如果快速获取失败,尝试获取data() - let data_result = tokio::time::timeout(Duration::from_secs(2), async { - let profiles = Config::profiles().await; - let data = profiles.latest_ref(); - IProfiles { - current: data.current.clone(), - items: data.items.clone(), - } - }) - .await; - - match data_result { - Ok(profiles) => { - logging!(info, Type::Cmd, "获取draft配置列表成功"); - return Ok(profiles); - } - Err(join_err) => { - logging!( - error, - Type::Cmd, - "获取draft配置任务失败或超时: {}", - join_err - ); - } - } - - // 策略3: fallback,尝试重新创建配置 - logging!(warn, Type::Cmd, "所有获取配置策略都失败,尝试fallback"); - - Ok(IProfiles::new().await) + logging!(debug, Type::Cmd, "获取配置文件列表"); + let draft = Config::profiles().await; + let latest = draft.latest_ref(); + Ok((**latest).clone()) } /// 增强配置文件 diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index f25e5990..bec3683c 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -69,23 +69,16 @@ impl IProfiles { } Err(err) => { logging!(error, Type::Config, "{err}"); - Self::template() + Self::default() } }, Err(err) => { logging!(error, Type::Config, "{err}"); - Self::template() + Self::default() } } } - pub fn template() -> Self { - Self { - items: Some(vec![]), - ..Self::default() - } - } - pub async fn save_file(&self) -> Result<()> { help::save_yaml( &dirs::profiles_path()?, @@ -101,12 +94,12 @@ impl IProfiles { self.items = Some(vec![]); } - if let Some(current) = patch.current + if let Some(current) = &patch.current && let Some(items) = self.items.as_ref() { let some_uid = Some(current); - if items.iter().any(|e| e.uid == some_uid) { - self.current = some_uid; + if items.iter().any(|e| e.uid.as_ref() == some_uid) { + self.current = some_uid.cloned(); } } diff --git a/src-tauri/src/feat/backup.rs b/src-tauri/src/feat/backup.rs index fcc8d048..4ccdecab 100644 --- a/src-tauri/src/feat/backup.rs +++ b/src-tauri/src/feat/backup.rs @@ -241,11 +241,15 @@ pub async fn restore_local_backup(filename: String) -> Result<()> { return Err(anyhow!("Backup file not found: {}", filename)); } - let verge = Config::verge().await; - let verge_data = verge.latest_ref().clone(); - let webdav_url = verge_data.webdav_url.clone(); - let webdav_username = verge_data.webdav_username.clone(); - let webdav_password = verge_data.webdav_password.clone(); + let (webdav_url, webdav_username, webdav_password) = { + let verge = Config::verge().await; + let verge = verge.latest_ref(); + ( + verge.webdav_url.clone(), + verge.webdav_username.clone(), + verge.webdav_password.clone(), + ) + }; let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&target_path)).await??; let mut zip = zip::ZipArchive::new(file)?; diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 61d4288c..97211210 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -365,7 +365,7 @@ async fn initialize_config_files() -> Result<()> { if let Ok(path) = dirs::profiles_path() && !path.exists() { - let template = IProfiles::template(); + let template = IProfiles::default(); help::save_yaml(&path, &template, Some("# Clash Verge")) .await .map_err(|e| anyhow::anyhow!("Failed to create profiles config: {}", e))?; diff --git a/src/services/types.d.ts b/src/services/types.d.ts index fcac1137..b5be4647 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -280,7 +280,6 @@ interface IProfileOption { interface IProfilesConfig { current?: string; - valid?: string[]; items?: IProfileItem[]; } From 501def669508bdead355b9255543c880431a21b4 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:46:37 +0800 Subject: [PATCH 03/10] refactor: update patch_config method to accept a reference to IProfiles --- src-tauri/src/cmd/profile.rs | 143 +++++-------------------------- src-tauri/src/config/profiles.rs | 2 +- 2 files changed, 24 insertions(+), 121 deletions(-) diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index aadc6798..c6a04fd6 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -15,13 +15,11 @@ use crate::{ ret_err, utils::{dirs, help, logging::Type}, }; +use scopeguard::defer; use smartstring::alias::String; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; -// 全局请求序列号跟踪,用于避免队列化执行 -static CURRENT_REQUEST_SEQUENCE: AtomicU64 = AtomicU64::new(0); - static CURRENT_SWITCHING_PROFILE: AtomicBool = AtomicBool::new(false); #[tauri::command] @@ -285,7 +283,7 @@ async fn restore_previous_profile(prev_profile: String) -> CmdResult<()> { Config::profiles() .await .draft_mut() - .patch_config(restore_profiles) + .patch_config(&restore_profiles) .stringify_err()?; Config::profiles().await.apply(); crate::process::AsyncHandler::spawn(|| async move { @@ -297,26 +295,7 @@ async fn restore_previous_profile(prev_profile: String) -> CmdResult<()> { Ok(()) } -async fn handle_success(current_sequence: u64, current_value: Option) -> CmdResult { - let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); - if current_sequence < latest_sequence { - logging!( - info, - Type::Cmd, - "内核操作后发现更新的请求 (序列号: {} < {}),忽略当前结果", - current_sequence, - latest_sequence - ); - Config::profiles().await.discard(); - return Ok(false); - } - - logging!( - info, - Type::Cmd, - "配置更新成功,序列号: {}", - current_sequence - ); +async fn handle_success(current_value: Option) -> CmdResult { Config::profiles().await.apply(); handle::Handle::refresh_clash(); @@ -333,17 +312,10 @@ async fn handle_success(current_sequence: u64, current_value: Option) -> } if let Some(current) = ¤t_value { - logging!( - info, - Type::Cmd, - "向前端发送配置变更事件: {}, 序列号: {}", - current, - current_sequence - ); + logging!(info, Type::Cmd, "向前端发送配置变更事件: {}", current,); handle::Handle::notify_profile_changed(current.clone()); } - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); Ok(true) } @@ -357,53 +329,31 @@ async fn handle_validation_failure( restore_previous_profile(prev_profile).await?; } handle::Handle::notice_message("config_validate::error", error_msg); - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); Ok(false) } -async fn handle_update_error(e: E, current_sequence: u64) -> CmdResult { - logging!( - warn, - Type::Cmd, - "更新过程发生错误: {}, 序列号: {}", - e, - current_sequence - ); +async fn handle_update_error(e: E) -> CmdResult { + logging!(warn, Type::Cmd, "更新过程发生错误: {}", e,); Config::profiles().await.discard(); handle::Handle::notice_message("config_validate::boot_error", e.to_string()); - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); Ok(false) } -async fn handle_timeout(current_profile: Option, current_sequence: u64) -> CmdResult { +async fn handle_timeout(current_profile: Option) -> CmdResult { let timeout_msg = "配置更新超时(30秒),可能是配置验证或核心通信阻塞"; - logging!( - error, - Type::Cmd, - "{}, 序列号: {}", - timeout_msg, - current_sequence - ); + logging!(error, Type::Cmd, "{}", timeout_msg); Config::profiles().await.discard(); if let Some(prev_profile) = current_profile { restore_previous_profile(prev_profile).await?; } handle::Handle::notice_message("config_validate::timeout", timeout_msg); - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); Ok(false) } async fn perform_config_update( - current_sequence: u64, current_value: Option, current_profile: Option, ) -> CmdResult { - logging!( - info, - Type::Cmd, - "开始内核配置更新,序列号: {}", - current_sequence - ); let update_result = tokio::time::timeout( Duration::from_secs(30), CoreManager::global().update_config(), @@ -411,46 +361,36 @@ async fn perform_config_update( .await; match update_result { - Ok(Ok((true, _))) => handle_success(current_sequence, current_value).await, + Ok(Ok((true, _))) => handle_success(current_value).await, Ok(Ok((false, error_msg))) => handle_validation_failure(error_msg, current_profile).await, - Ok(Err(e)) => handle_update_error(e, current_sequence).await, - Err(_) => handle_timeout(current_profile, current_sequence).await, + Ok(Err(e)) => handle_update_error(e).await, + Err(_) => handle_timeout(current_profile).await, } } /// 修改profiles的配置 #[tauri::command] pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { - if CURRENT_SWITCHING_PROFILE.load(Ordering::SeqCst) { + if CURRENT_SWITCHING_PROFILE + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { logging!(info, Type::Cmd, "当前正在切换配置,放弃请求"); - return Ok(false); + return Err("switch_in_progress".into()); } - CURRENT_SWITCHING_PROFILE.store(true, Ordering::SeqCst); - // 为当前请求分配序列号 - let current_sequence = CURRENT_REQUEST_SEQUENCE.fetch_add(1, Ordering::SeqCst) + 1; + defer! { + CURRENT_SWITCHING_PROFILE.store(false, Ordering::Release); + } let target_profile = profiles.current.clone(); logging!( info, Type::Cmd, - "开始修改配置文件,请求序列号: {}, 目标profile: {:?}", - current_sequence, + "开始修改配置文件,目标profile: {:?}", target_profile ); - let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); - if current_sequence < latest_sequence { - logging!( - info, - Type::Cmd, - "获取锁后发现更新的请求 (序列号: {} < {}),放弃当前请求", - current_sequence, - latest_sequence - ); - return Ok(false); - } - // 保存当前配置,以便在验证失败时恢复 let current_profile = Config::profiles().await.latest_ref().current.clone(); logging!(info, Type::Cmd, "当前配置: {:?}", current_profile); @@ -460,50 +400,13 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { && current_profile.as_ref() != Some(new_profile) && validate_new_profile(new_profile).await.is_err() { - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); return Ok(false); } - // 检查请求有效性 - let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); - if current_sequence < latest_sequence { - logging!( - info, - Type::Cmd, - "在核心操作前发现更新的请求 (序列号: {} < {}),放弃当前请求", - current_sequence, - latest_sequence - ); - return Ok(false); - } - - // 更新profiles配置 - logging!( - info, - Type::Cmd, - "正在更新配置草稿,序列号: {}", - current_sequence - ); - + let _ = Config::profiles().await.draft_mut().patch_config(&profiles); let current_value = profiles.current.clone(); - let _ = Config::profiles().await.draft_mut().patch_config(profiles); - - // 在调用内核前再次验证请求有效性 - let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); - if current_sequence < latest_sequence { - logging!( - info, - Type::Cmd, - "在内核交互前发现更新的请求 (序列号: {} < {}),放弃当前请求", - current_sequence, - latest_sequence - ); - Config::profiles().await.discard(); - return Ok(false); - } - - perform_config_update(current_sequence, current_value, current_profile).await + perform_config_update(current_value, current_profile).await } /// 根据profile name修改profiles diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index bec3683c..cc7f5c80 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -89,7 +89,7 @@ impl IProfiles { } /// 只修改current,valid和chain - pub fn patch_config(&mut self, patch: IProfiles) -> Result<()> { + pub fn patch_config(&mut self, patch: &IProfiles) -> Result<()> { if self.items.is_none() { self.items = Some(vec![]); } From 8f4c0e823b70db842c2fb1f4586afef644d6b668 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:53:55 +0800 Subject: [PATCH 04/10] refactor: remove update semaphore from CoreManager and related config update logic --- src-tauri/src/core/manager/config.rs | 5 ----- src-tauri/src/core/manager/mod.rs | 3 --- 2 files changed, 8 deletions(-) diff --git a/src-tauri/src/core/manager/config.rs b/src-tauri/src/core/manager/config.rs index 70b4bc6f..63455db8 100644 --- a/src-tauri/src/core/manager/config.rs +++ b/src-tauri/src/core/manager/config.rs @@ -39,11 +39,6 @@ impl CoreManager { return Ok((true, String::new())); } - let _permit = self - .update_semaphore - .try_acquire() - .map_err(|_| anyhow!("Config update already in progress"))?; - self.perform_config_update().await } diff --git a/src-tauri/src/core/manager/mod.rs b/src-tauri/src/core/manager/mod.rs index 05220f9e..fbd49d05 100644 --- a/src-tauri/src/core/manager/mod.rs +++ b/src-tauri/src/core/manager/mod.rs @@ -5,7 +5,6 @@ mod state; use anyhow::Result; use parking_lot::Mutex; use std::{fmt, sync::Arc, time::Instant}; -use tokio::sync::Semaphore; use crate::process::CommandChildGuard; use crate::singleton_lazy; @@ -30,7 +29,6 @@ impl fmt::Display for RunningMode { #[derive(Debug)] pub struct CoreManager { state: Arc>, - update_semaphore: Arc, last_update: Arc>>, } @@ -53,7 +51,6 @@ impl Default for CoreManager { fn default() -> Self { Self { state: Arc::new(Mutex::new(State::default())), - update_semaphore: Arc::new(Semaphore::new(1)), last_update: Arc::new(Mutex::new(None)), } } From 5d0b18eecddb9bc4af3b2bb61232ccb71269b95d Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:20:52 +0800 Subject: [PATCH 05/10] docs: update contributor acknowledgment in UPDATELOG.md --- UPDATELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index 2fa6bc73..a8d29907 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -1,6 +1,6 @@ ## v2.4.3 -感谢 @Slinetrac, @oomeow 以及 @Lythrilla 的出色贡献 +感谢 @Slinetrac, @oomeow, @Lythrilla, @Dragon1573 的出色贡献 ### 🐞 修复问题 From 54039c24489cda8a9e1b3395de4adaa628d0b068 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:24:27 +0800 Subject: [PATCH 06/10] fix: correct download URL for ARM64 Windows setup in autobuild.yml --- .github/workflows/autobuild.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 50c6617b..96ead562 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -90,7 +90,7 @@ jobs: ### Windows (不再支持Win7) #### 正常版本(推荐) - - [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe) + - [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe) #### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用) - [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe) From 4babc727549ae6f69d686826d02075942356c034 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:32:36 +0800 Subject: [PATCH 07/10] feat: integrate arc-swap for improved concurrency in CoreManager and Hotkey(onMac) management --- src-tauri/Cargo.lock | 7 +++++ src-tauri/Cargo.toml | 1 + src-tauri/src/core/backup.rs | 35 ++++++++++++----------- src-tauri/src/core/hotkey.rs | 22 ++++++--------- src-tauri/src/core/manager/config.rs | 8 +++--- src-tauri/src/core/manager/mod.rs | 42 ++++++++++++++++++++-------- src-tauri/src/core/manager/state.rs | 3 +- src-tauri/src/lib.rs | 19 +++++++++---- src-tauri/src/utils/resolve/mod.rs | 2 +- 9 files changed, 84 insertions(+), 55 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4c01e031..d433be36 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -152,6 +152,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arraydeque" version = "0.5.1" @@ -1093,6 +1099,7 @@ version = "2.4.3" dependencies = [ "aes-gcm", "anyhow", + "arc-swap", "async-trait", "backoff", "base64 0.22.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b5e3a424..db31304e 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -86,6 +86,7 @@ smartstring = { version = "1.0.1", features = ["serde"] } clash_verge_service_ipc = { version = "2.0.21", features = [ "client", ], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" } +arc-swap = "1.7.1" [target.'cfg(windows)'.dependencies] runas = "=1.2.0" diff --git a/src-tauri/src/core/backup.rs b/src-tauri/src/core/backup.rs index cf90bdd1..6e48a137 100644 --- a/src-tauri/src/core/backup.rs +++ b/src-tauri/src/core/backup.rs @@ -6,8 +6,8 @@ use crate::{ utils::{dirs, logging::Type}, }; use anyhow::Error; +use arc_swap::{ArcSwap, ArcSwapOption}; use once_cell::sync::OnceCell; -use parking_lot::Mutex; use reqwest_dav::list_cmd::{ListEntity, ListFile}; use smartstring::alias::String; use std::{ @@ -56,24 +56,24 @@ impl Operation { } pub struct WebDavClient { - config: Arc>>, - clients: Arc>>, + config: Arc>, + clients: Arc>>, } impl WebDavClient { pub fn global() -> &'static WebDavClient { static WEBDAV_CLIENT: OnceCell = OnceCell::new(); WEBDAV_CLIENT.get_or_init(|| WebDavClient { - config: Arc::new(Mutex::new(None)), - clients: Arc::new(Mutex::new(HashMap::new())), + config: Arc::new(ArcSwapOption::new(None)), + clients: Arc::new(ArcSwap::new(Arc::new(HashMap::new()))), }) } async fn get_client(&self, op: Operation) -> Result { // 先尝试从缓存获取 { - let clients = self.clients.lock(); - if let Some(client) = clients.get(&op) { + let clients_map = self.clients.load(); + if let Some(client) = clients_map.get(&op) { return Ok(client.clone()); } } @@ -81,10 +81,10 @@ impl WebDavClient { // 获取或创建配置 let config = { // 首先检查是否已有配置 - let existing_config = self.config.lock().as_ref().cloned(); + let existing_config = self.config.load(); - if let Some(cfg) = existing_config { - cfg + if let Some(cfg_arc) = existing_config.clone() { + (*cfg_arc).clone() } else { // 释放锁后获取异步配置 let verge = Config::verge().await.latest_ref().clone(); @@ -106,8 +106,8 @@ impl WebDavClient { password: verge.webdav_password.unwrap_or_default(), }; - // 重新获取锁并存储配置 - *self.config.lock() = Some(config.clone()); + // 存储配置到 ArcSwapOption + self.config.store(Some(Arc::new(config.clone()))); config } }; @@ -161,18 +161,19 @@ impl WebDavClient { } } - // 缓存客户端 + // 缓存客户端(替换 Arc>> 的写法) { - let mut clients = self.clients.lock(); - clients.insert(op, client.clone()); + let mut map = (**self.clients.load()).clone(); + map.insert(op, client.clone()); + self.clients.store(map.into()); } Ok(client) } pub fn reset(&self) { - *self.config.lock() = None; - self.clients.lock().clear(); + self.config.store(None); + self.clients.store(Arc::new(HashMap::new())); } pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> { diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index d9429c76..f33a2cbc 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -5,7 +5,7 @@ use crate::{ singleton_with_logging, utils::logging::Type, }; use anyhow::{Result, bail}; -use parking_lot::Mutex; +use arc_swap::ArcSwap; use smartstring::alias::String; use std::{collections::HashMap, fmt, str::FromStr, sync::Arc}; use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState}; @@ -93,13 +93,13 @@ impl SystemHotkey { } pub struct Hotkey { - current: Arc>>, + current: ArcSwap>, } impl Hotkey { fn new() -> Self { Self { - current: Arc::new(Mutex::new(Vec::new())), + current: ArcSwap::new(Arc::new(Vec::new())), } } @@ -272,9 +272,9 @@ impl Hotkey { singleton_with_logging!(Hotkey, INSTANCE, "Hotkey"); impl Hotkey { - pub async fn init(&self) -> Result<()> { + pub async fn init(&self, skip: bool) -> Result<()> { let verge = Config::verge().await; - let enable_global_hotkey = verge.latest_ref().enable_global_hotkey.unwrap_or(true); + let enable_global_hotkey = !skip && verge.latest_ref().enable_global_hotkey.unwrap_or(true); logging!( debug, @@ -283,10 +283,6 @@ impl Hotkey { enable_global_hotkey ); - if !enable_global_hotkey { - return Ok(()); - } - // Extract hotkeys data before async operations let hotkeys = verge.latest_ref().hotkeys.as_ref().cloned(); @@ -344,7 +340,7 @@ impl Hotkey { } } } - self.current.lock().clone_from(&hotkeys); + self.current.store(Arc::new(hotkeys)); } else { logging!(debug, Type::Hotkey, "No hotkeys configured"); } @@ -375,8 +371,8 @@ impl Hotkey { pub async fn update(&self, new_hotkeys: Vec) -> Result<()> { // Extract current hotkeys before async operations - let current_hotkeys = self.current.lock().clone(); - let old_map = Self::get_map_from_vec(¤t_hotkeys); + let current_hotkeys = &*self.current.load(); + let old_map = Self::get_map_from_vec(current_hotkeys); let new_map = Self::get_map_from_vec(&new_hotkeys); let (del, add) = Self::get_diff(old_map, new_map); @@ -390,7 +386,7 @@ impl Hotkey { } // Update the current hotkeys after all async operations - *self.current.lock() = new_hotkeys; + self.current.store(Arc::new(new_hotkeys)); Ok(()) } diff --git a/src-tauri/src/core/manager/config.rs b/src-tauri/src/core/manager/config.rs index 63455db8..b0e79bca 100644 --- a/src-tauri/src/core/manager/config.rs +++ b/src-tauri/src/core/manager/config.rs @@ -44,15 +44,15 @@ impl CoreManager { fn should_update_config(&self) -> Result { let now = Instant::now(); - let mut last = self.last_update.lock(); + let last = self.get_last_update(); - if let Some(last_time) = *last - && now.duration_since(last_time) < timing::CONFIG_UPDATE_DEBOUNCE + if let Some(last_time) = last + && now.duration_since(*last_time) < timing::CONFIG_UPDATE_DEBOUNCE { return Ok(false); } - *last = Some(now); + self.set_last_update(now); Ok(true) } diff --git a/src-tauri/src/core/manager/mod.rs b/src-tauri/src/core/manager/mod.rs index fbd49d05..8f41af64 100644 --- a/src-tauri/src/core/manager/mod.rs +++ b/src-tauri/src/core/manager/mod.rs @@ -3,7 +3,7 @@ mod lifecycle; mod state; use anyhow::Result; -use parking_lot::Mutex; +use arc_swap::{ArcSwap, ArcSwapOption}; use std::{fmt, sync::Arc, time::Instant}; use crate::process::CommandChildGuard; @@ -28,21 +28,21 @@ impl fmt::Display for RunningMode { #[derive(Debug)] pub struct CoreManager { - state: Arc>, - last_update: Arc>>, + state: ArcSwap, + last_update: ArcSwapOption, } #[derive(Debug)] struct State { - running_mode: Arc, - child_sidecar: Option, + running_mode: ArcSwap, + child_sidecar: ArcSwapOption, } impl Default for State { fn default() -> Self { Self { - running_mode: Arc::new(RunningMode::NotRunning), - child_sidecar: None, + running_mode: ArcSwap::new(Arc::new(RunningMode::NotRunning)), + child_sidecar: ArcSwapOption::new(None), } } } @@ -50,23 +50,41 @@ impl Default for State { impl Default for CoreManager { fn default() -> Self { Self { - state: Arc::new(Mutex::new(State::default())), - last_update: Arc::new(Mutex::new(None)), + state: ArcSwap::new(Arc::new(State::default())), + last_update: ArcSwapOption::new(None), } } } impl CoreManager { pub fn get_running_mode(&self) -> Arc { - Arc::clone(&self.state.lock().running_mode) + Arc::clone(&self.state.load().running_mode.load()) + } + + pub fn take_child_sidecar(&self) -> Option { + self.state + .load() + .child_sidecar + .swap(None) + .and_then(|arc| Arc::try_unwrap(arc).ok()) + } + + pub fn get_last_update(&self) -> Option> { + self.last_update.load_full() } pub fn set_running_mode(&self, mode: RunningMode) { - self.state.lock().running_mode = Arc::new(mode); + let state = self.state.load(); + state.running_mode.store(Arc::new(mode)); } pub fn set_running_child_sidecar(&self, child: CommandChildGuard) { - self.state.lock().child_sidecar = Some(child); + let state = self.state.load(); + state.child_sidecar.store(Some(Arc::new(child))); + } + + pub fn set_last_update(&self, time: Instant) { + self.last_update.store(Some(Arc::new(time))); } pub async fn init(&self) -> Result<()> { diff --git a/src-tauri/src/core/manager/state.rs b/src-tauri/src/core/manager/state.rs index d38a148c..af92ce18 100644 --- a/src-tauri/src/core/manager/state.rs +++ b/src-tauri/src/core/manager/state.rs @@ -93,8 +93,7 @@ impl CoreManager { defer! { self.set_running_mode(RunningMode::NotRunning); } - let mut state = self.state.lock(); - if let Some(child) = state.child_sidecar.take() { + if let Some(child) = self.take_child_sidecar() { let pid = child.pid(); drop(child); logging!(trace, Type::Core, "Sidecar stopped (PID: {:?})", pid); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c6db41c0..da6c5710 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -334,10 +334,7 @@ pub fn run() { .register_system_hotkey(SystemHotkey::CmdW) .await; } - - if !is_enable_global_hotkey { - let _ = hotkey::Hotkey::global().init().await; - } + let _ = hotkey::Hotkey::global().init(true).await; return; } @@ -358,8 +355,18 @@ pub fn run() { #[cfg(target_os = "macos")] { use crate::core::hotkey::SystemHotkey; - let _ = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdQ); - let _ = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW); + AsyncHandler::spawn(move || async move { + let _ = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdQ); + let _ = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW); + let is_enable_global_hotkey = Config::verge() + .await + .latest_ref() + .enable_global_hotkey + .unwrap_or(true); + if !is_enable_global_hotkey { + let _ = hotkey::Hotkey::global().reset(); + } + }); } } } diff --git a/src-tauri/src/utils/resolve/mod.rs b/src-tauri/src/utils/resolve/mod.rs index cb0989b1..4055ecd0 100644 --- a/src-tauri/src/utils/resolve/mod.rs +++ b/src-tauri/src/utils/resolve/mod.rs @@ -120,7 +120,7 @@ pub(super) async fn init_timer() { } pub(super) async fn init_hotkey() { - logging_error!(Type::Setup, Hotkey::global().init().await); + logging_error!(Type::Setup, Hotkey::global().init(false).await); } pub(super) async fn init_auto_lightweight_boot() { From 940717712a9ab2aec420d1ce77ffa56dd312df23 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:40:35 +0800 Subject: [PATCH 08/10] chore(deps): update dependency @types/node to ^24.10.0 (#5292) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 31b65797..1cfd8cde 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@tauri-apps/cli": "2.9.2", "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", - "@types/node": "^24.9.2", + "@types/node": "^24.10.0", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "@vitejs/plugin-legacy": "^7.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da35abee..77cce79d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,8 +154,8 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^24.9.2 - version: 24.9.2 + specifier: ^24.10.0 + version: 24.10.0 '@types/react': specifier: 19.2.2 version: 19.2.2 @@ -164,10 +164,10 @@ importers: version: 19.2.2(@types/react@19.2.2) '@vitejs/plugin-legacy': specifier: ^7.2.1 - version: 7.2.1(terser@5.44.0)(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) + version: 7.2.1(terser@5.44.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) '@vitejs/plugin-react-swc': specifier: ^4.2.0 - version: 4.2.0(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) + version: 4.2.0(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -248,16 +248,16 @@ importers: version: 8.46.2(eslint@9.39.0(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^7.1.12 - version: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + version: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) vite-plugin-monaco-editor-esm: specifier: ^2.0.2 version: 2.0.2(monaco-editor@0.54.0) vite-plugin-svgr: specifier: ^4.5.0 - version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) + version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) vitest: specifier: ^4.0.6 - version: 4.0.6(@types/debug@4.1.12)(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + version: 4.0.6(@types/debug@4.1.12)(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) packages: @@ -1821,8 +1821,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.9.2': - resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==} + '@types/node@24.10.0': + resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -5982,7 +5982,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@24.9.2': + '@types/node@24.10.0': dependencies: undici-types: 7.16.0 @@ -6160,7 +6160,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-legacy@7.2.1(terser@5.44.0)(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1))': + '@vitejs/plugin-legacy@7.2.1(terser@5.44.0)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4) @@ -6175,15 +6175,15 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.44.0 - vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react-swc@4.2.0(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1))': + '@vitejs/plugin-react-swc@4.2.0(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.43 '@swc/core': 1.14.0 - vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - '@swc/helpers' @@ -6196,13 +6196,13 @@ snapshots: chai: 6.2.0 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.6(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/mocker@4.0.6(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 4.0.6 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) '@vitest/pretty-format@4.0.6': dependencies: @@ -8844,18 +8844,18 @@ snapshots: dependencies: monaco-editor: 0.54.0 - vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)): + vite-plugin-svgr@4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)): dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.46.2) '@svgr/core': 8.1.0(typescript@5.9.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1): + vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1): dependencies: esbuild: 0.25.4 fdir: 6.5.0(picomatch@4.0.3) @@ -8864,17 +8864,17 @@ snapshots: rollup: 4.46.2 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.9.2 + '@types/node': 24.10.0 fsevents: 2.3.3 jiti: 2.6.1 sass: 1.93.3 terser: 5.44.0 yaml: 2.8.1 - vitest@4.0.6(@types/debug@4.1.12)(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1): + vitest@4.0.6(@types/debug@4.1.12)(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.6 - '@vitest/mocker': 4.0.6(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) + '@vitest/mocker': 4.0.6(vite@7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1)) '@vitest/pretty-format': 4.0.6 '@vitest/runner': 4.0.6 '@vitest/snapshot': 4.0.6 @@ -8891,11 +8891,11 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) + vite: 7.1.12(@types/node@24.10.0)(jiti@2.6.1)(sass@1.93.3)(terser@5.44.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 24.9.2 + '@types/node': 24.10.0 transitivePeerDependencies: - jiti - less From 10031184681c3ef19af86973b0daf478979ef159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9D=A4=E6=98=AF=E7=BA=B1=E9=9B=BE=E9=85=B1=E5=93=9F?= =?UTF-8?q?=EF=BD=9E?= <49941141+Dragon1573@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:35:23 +0800 Subject: [PATCH 09/10] docs(release): Assets URL correction (#5294) - Fix mis-spelling of assets URL to Linux RPM package --- .github/workflows/autobuild.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 96ead562..d9b308bd 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -578,7 +578,7 @@ jobs: - [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb) #### RPM包(Redhat系) 使用 dnf ./路径 安装 - - [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.armhfp.rpm) + - [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm) ### FAQ - [常见问题](https://clash-verge-rev.github.io/faq/windows.html) From 48a19f99e232e9b1628dd7317610da07f940019d Mon Sep 17 00:00:00 2001 From: Slinetrac Date: Mon, 3 Nov 2025 12:43:48 +0800 Subject: [PATCH 10/10] docs: UPDATELOG.md --- UPDATELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPDATELOG.md b/UPDATELOG.md index a8d29907..c1d12deb 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -36,6 +36,7 @@ - 修复 macOS 从 Dock 栏退出轻量模式状态不同步 - 修复 Linux 系统主题切换不生效 - 修复 `允许自动更新` 字段使手动订阅刷新失效 +- 修复轻量模式托盘状态不同步
✨ 新增功能