From 094feb74ec8f4d5b8ca8722114c3c753df14c69f Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:31:59 +0800 Subject: [PATCH 01/15] feat: add new monochrome tray icons for system and tunnel --- UPDATELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index 2cf97bb57d..c43f559ce8 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -3,11 +3,12 @@ ### 🐞 修复问题 - 修复系统代理端口不同步问题 -- 修复 自定义 `css` 背景图无法生效问题 +- 修复自定义 `css` 背景图无法生效问题 ### ✨ 新增功能 - `sidecar` 模式下清理多余的内核进程,防止运行出现异常 +- 新 macOS 下 TUN 和系统代理模式托盘图标(暂测) ### 🚀 优化改进 From a92872c8317974350007880927fdbb37b7da35bf Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 22 Jun 2025 15:42:01 +0800 Subject: [PATCH 02/15] fix: resolve race condition freeze on rapid tray icon clicks in lightweight mode --- UPDATELOG.md | 1 + src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/core/hotkey.rs | 90 +++---------------- src-tauri/src/core/tray/mod.rs | 42 +++++++-- src-tauri/src/feat/window.rs | 37 +++++++- src-tauri/src/module/lightweight.rs | 27 +++++- src-tauri/src/utils/resolve.rs | 10 ++- src-tauri/src/utils/window_manager.rs | 124 +++++++++++++++++++++++--- 9 files changed, 229 insertions(+), 104 deletions(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index c43f559ce8..973785cc11 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -4,6 +4,7 @@ - 修复系统代理端口不同步问题 - 修复自定义 `css` 背景图无法生效问题 +- 修复在轻量模式下快速点击托盘图标带来的竞争态卡死问题 ### ✨ 新增功能 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 173f7a46f6..659f5dabc6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1078,6 +1078,7 @@ dependencies = [ "reqwest", "reqwest_dav", "runas", + "scopeguard", "serde", "serde_json", "serde_yaml", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6908bba03c..843ce20514 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -80,6 +80,7 @@ gethostname = "1.0.2" hmac = "0.12.1" sha2 = "0.10.9" hex = "0.4.3" +scopeguard = "1.2.0" [target.'cfg(windows)'.dependencies] runas = "=1.2.0" diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 070a049bbe..33c8fa965c 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -1,10 +1,6 @@ use crate::{ - config::Config, - core::handle, - feat, logging, logging_error, - module::lightweight::entry_lightweight_mode, - process::AsyncHandler, - utils::{logging::Type, resolve}, + config::Config, core::handle, feat, logging, logging_error, + module::lightweight::entry_lightweight_mode, utils::logging::Type, }; use anyhow::{bail, Result}; use once_cell::sync::OnceCell; @@ -14,7 +10,7 @@ use tauri::Manager; use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState}; pub struct Hotkey { - current: Arc>>, // 保存当前的热键设置 + current: Arc>>, } impl Hotkey { @@ -38,7 +34,6 @@ impl Hotkey { enable_global_hotkey ); - // 如果全局热键被禁用,则不注册热键 if !enable_global_hotkey { return Ok(()); } @@ -153,76 +148,14 @@ impl Hotkey { "=== Hotkey Dashboard Window Operation Start ===" ); - // 检查是否在轻量模式下,如果是,需要同步处理 - if crate::module::lightweight::is_in_lightweight_mode() { - logging!( - info, - Type::Hotkey, - true, - "In lightweight mode, calling open_or_close_dashboard directly" - ); - crate::feat::open_or_close_dashboard(); - } else { - AsyncHandler::spawn(move || async move { - logging!( - debug, - Type::Hotkey, - true, - "Toggle dashboard window visibility (async)" - ); + logging!( + info, + Type::Hotkey, + true, + "Using unified WindowManager for hotkey operation (bypass debounce)" + ); - // 检查窗口是否存在 - if let Some(window) = handle::Handle::global().get_window() { - // 如果窗口可见,则隐藏 - match window.is_visible() { - Ok(visible) => { - if visible { - logging!( - info, - Type::Window, - true, - "Window is visible, hiding it" - ); - let _ = window.hide(); - } else { - // 如果窗口不可见,则显示 - logging!( - info, - Type::Window, - true, - "Window is hidden, showing it" - ); - if window.is_minimized().unwrap_or(false) { - let _ = window.unminimize(); - } - let _ = window.show(); - let _ = window.set_focus(); - } - } - Err(e) => { - logging!( - warn, - Type::Window, - true, - "Failed to check window visibility: {}", - e - ); - let _ = window.show(); - let _ = window.set_focus(); - } - } - } else { - // 如果窗口不存在,创建一个新窗口 - logging!( - info, - Type::Window, - true, - "Window does not exist, creating a new one" - ); - resolve::create_window(true); - } - }); - } + crate::feat::open_or_close_dashboard_hotkey(); logging!( debug, @@ -261,10 +194,8 @@ impl Hotkey { } } } else { - // 直接执行函数,不做任何状态检查 logging!(debug, Type::Hotkey, "Executing function directly"); - // 获取全局热键状态 let is_enable_global_hotkey = Config::verge() .latest() .enable_global_hotkey @@ -274,7 +205,6 @@ impl Hotkey { f(); } else { use crate::utils::window_manager::WindowManager; - // 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键 let is_visible = WindowManager::is_main_window_visible(); let is_focused = WindowManager::is_main_window_focused(); diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index de10715e04..98c46004e1 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -39,6 +39,29 @@ use super::handle; #[derive(Clone)] struct TrayState {} +// 托盘点击防抖机制 +static TRAY_CLICK_DEBOUNCE: OnceCell> = OnceCell::new(); +const TRAY_CLICK_DEBOUNCE_MS: u64 = 300; + +fn get_tray_click_debounce() -> &'static Mutex { + TRAY_CLICK_DEBOUNCE.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(1))) +} + +fn should_handle_tray_click() -> bool { + let debounce_lock = get_tray_click_debounce(); + let mut last_click = debounce_lock.lock(); + let now = Instant::now(); + + if now.duration_since(*last_click) >= Duration::from_millis(TRAY_CLICK_DEBOUNCE_MS) { + *last_click = now; + true + } else { + log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms", + now.duration_since(*last_click).as_millis()); + false + } +} + #[cfg(target_os = "macos")] pub struct Tray { pub speed_rate: Arc>>, @@ -664,6 +687,11 @@ impl Tray { .. } = event { + // 添加防抖检查,防止快速连击 + if !should_handle_tray_click() { + return; + } + match tray_event.as_str() { "system_proxy" => feat::toggle_system_proxy(), "tun_mode" => feat::toggle_tun_mode(None), @@ -949,12 +977,15 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { "open_window" => { use crate::utils::window_manager::WindowManager; log::info!(target: "app", "托盘菜单点击: 打开窗口"); - // 如果在轻量模式中,先退出轻量模式 + + if !should_handle_tray_click() { + return; + } + if crate::module::lightweight::is_in_lightweight_mode() { log::info!(target: "app", "当前在轻量模式,正在退出"); crate::module::lightweight::exit_lightweight_mode(); } - // 使用统一的窗口管理器显示窗口 let result = WindowManager::show_main_window(); log::info!(target: "app", "窗口显示结果: {:?}", result); } @@ -977,7 +1008,10 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { "restart_clash" => feat::restart_clash_core(), "restart_app" => feat::restart_app(), "entry_lightweight_mode" => { - // 处理轻量模式的切换 + if !should_handle_tray_click() { + return; + } + let was_lightweight = crate::module::lightweight::is_in_lightweight_mode(); if was_lightweight { crate::module::lightweight::exit_lightweight_mode(); @@ -985,7 +1019,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { crate::module::lightweight::entry_lightweight_mode(); } - // 退出轻量模式后显示主窗口 if was_lightweight { use crate::utils::window_manager::WindowManager; let result = WindowManager::show_main_window(); @@ -1002,7 +1035,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { _ => {} } - // 统一调用状态更新 if let Err(e) = Tray::global().update_all_states() { log::warn!(target: "app", "更新托盘状态失败: {}", e); } diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index 4ac7505818..9d43ff4d56 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -11,11 +11,43 @@ use crate::{ /// Open or close the dashboard window #[allow(dead_code)] pub fn open_or_close_dashboard() { + open_or_close_dashboard_internal(false) +} + +/// Open or close the dashboard window (hotkey call, dispatched to main thread) +#[allow(dead_code)] +pub fn open_or_close_dashboard_hotkey() { + open_or_close_dashboard_internal(true) +} + +/// Internal implementation for opening/closing dashboard +fn open_or_close_dashboard_internal(bypass_debounce: bool) { + use crate::process::AsyncHandler; use crate::utils::window_manager::WindowManager; - log::info!(target: "app", "Attempting to open/close dashboard"); + log::info!(target: "app", "Attempting to open/close dashboard (绕过防抖: {})", bypass_debounce); - // 检查是否在轻量模式下 + // 热键调用调度到主线程执行,避免 WebView 创建死锁 + if bypass_debounce { + log::info!(target: "app", "热键调用,调度到主线程执行窗口操作"); + + AsyncHandler::spawn(move || async move { + log::info!(target: "app", "主线程中执行热键窗口操作"); + + if crate::module::lightweight::is_in_lightweight_mode() { + log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode"); + crate::module::lightweight::exit_lightweight_mode(); + log::info!(target: "app", "Creating new window after exiting lightweight mode"); + let result = WindowManager::show_main_window(); + log::info!(target: "app", "Window operation result: {:?}", result); + return; + } + + let result = WindowManager::toggle_main_window(); + log::info!(target: "app", "Window toggle result: {:?}", result); + }); + return; + } if crate::module::lightweight::is_in_lightweight_mode() { log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode"); crate::module::lightweight::exit_lightweight_mode(); @@ -25,7 +57,6 @@ pub fn open_or_close_dashboard() { return; } - // 使用统一的窗口管理器切换窗口状态 let result = WindowManager::toggle_main_window(); log::info!(target: "app", "Window toggle result: {:?}", result); } diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index b6ebed7cf5..f93cc170c2 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -13,11 +13,17 @@ use crate::AppHandleManager; use anyhow::{Context, Result}; use delay_timer::prelude::TaskBuilder; -use std::sync::Mutex; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, +}; use tauri::{Listener, Manager}; const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task"; +// 添加退出轻量模式的锁,防止并发调用 +static EXITING_LIGHTWEIGHT: AtomicBool = AtomicBool::new(false); + fn with_lightweight_status(f: F) -> R where F: FnOnce(&mut LightWeightState) -> R, @@ -131,6 +137,25 @@ pub fn entry_lightweight_mode() { // 添加从轻量模式恢复的函数 pub fn exit_lightweight_mode() { + // 使用原子操作检查是否已经在退出过程中,防止并发调用 + if EXITING_LIGHTWEIGHT + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + logging!( + info, + Type::Lightweight, + true, + "轻量模式退出操作已在进行中,跳过重复调用" + ); + return; + } + + // 使用defer确保无论如何都会重置标志 + let _guard = scopeguard::guard((), |_| { + EXITING_LIGHTWEIGHT.store(false, Ordering::SeqCst); + }); + // 确保当前确实处于轻量模式才执行退出操作 if !is_in_lightweight_mode() { logging!(info, Type::Lightweight, true, "当前不在轻量模式,无需退出"); diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 8e1c0ca7ac..dada58ec65 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -13,6 +13,7 @@ use anyhow::{bail, Result}; use once_cell::sync::OnceCell; use parking_lot::{Mutex, RwLock}; use percent_encoding::percent_decode_str; +use scopeguard; use serde_yaml::Mapping; use std::{ sync::Arc, @@ -337,6 +338,12 @@ pub fn create_window(is_show: bool) -> bool { *creating = (true, Instant::now()); + // ScopeGuard 确保创建状态重置,防止 webview 卡死 + let _guard = scopeguard::guard(creating, |mut creating_guard| { + *creating_guard = (false, Instant::now()); + logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置"); + }); + match tauri::WebviewWindowBuilder::new( &handle::Handle::global().app_handle().unwrap(), "main", /* the unique window label */ @@ -419,8 +426,6 @@ pub fn create_window(is_show: bool) -> bool { Ok(newly_created_window) => { logging!(debug, Type::Window, true, "主窗口实例创建成功"); - *creating = (false, Instant::now()); - update_ui_ready_stage(UiReadyStage::NotStarted); AsyncHandler::spawn(move || async move { @@ -534,7 +539,6 @@ pub fn create_window(is_show: bool) -> bool { } Err(e) => { logging!(error, Type::Window, true, "主窗口构建失败: {}", e); - *creating = (false, Instant::now()); // Reset the creating state if window creation failed false } } diff --git a/src-tauri/src/utils/window_manager.rs b/src-tauri/src/utils/window_manager.rs index d00656f647..c06cb5190f 100644 --- a/src-tauri/src/utils/window_manager.rs +++ b/src-tauri/src/utils/window_manager.rs @@ -4,6 +4,14 @@ use tauri::{Manager, WebviewWindow, Wry}; #[cfg(target_os = "macos")] use crate::AppHandleManager; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use scopeguard; +use std::{ + sync::atomic::{AtomicBool, Ordering}, + time::{Duration, Instant}, +}; + /// 窗口操作结果 #[derive(Debug, Clone, Copy, PartialEq)] pub enum WindowOperationResult { @@ -34,6 +42,45 @@ pub enum WindowState { NotExist, } +// 窗口操作防抖机制 +static WINDOW_OPERATION_DEBOUNCE: OnceCell> = OnceCell::new(); +static WINDOW_OPERATION_IN_PROGRESS: AtomicBool = AtomicBool::new(false); +const WINDOW_OPERATION_DEBOUNCE_MS: u64 = 500; + +fn get_window_operation_debounce() -> &'static Mutex { + WINDOW_OPERATION_DEBOUNCE.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(1))) +} + +fn should_handle_window_operation() -> bool { + if WINDOW_OPERATION_IN_PROGRESS.load(Ordering::Acquire) { + log::warn!(target: "app", "[防抖] 窗口操作已在进行中,跳过重复调用"); + return false; + } + + let debounce_lock = get_window_operation_debounce(); + let mut last_operation = debounce_lock.lock(); + let now = Instant::now(); + let elapsed = now.duration_since(*last_operation); + + log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)", + elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS); + + if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) { + *last_operation = now; + WINDOW_OPERATION_IN_PROGRESS.store(true, Ordering::Release); + log::info!(target: "app", "[防抖] 窗口操作被允许执行"); + true + } else { + log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms", + elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS); + false + } +} + +fn finish_window_operation() { + WINDOW_OPERATION_IN_PROGRESS.store(false, Ordering::Release); +} + /// 统一的窗口管理器 pub struct WindowManager; @@ -65,6 +112,14 @@ impl WindowManager { /// 智能显示主窗口 pub fn show_main_window() -> WindowOperationResult { + // 防抖检查 + if !should_handle_window_operation() { + return WindowOperationResult::NoAction; + } + let _guard = scopeguard::guard((), |_| { + finish_window_operation(); + }); + logging!(info, Type::Window, true, "开始智能显示主窗口"); logging!( debug, @@ -80,8 +135,11 @@ impl WindowManager { WindowState::NotExist => { logging!(info, Type::Window, true, "窗口不存在,创建新窗口"); if Self::create_new_window() { + logging!(info, Type::Window, true, "窗口创建成功"); + std::thread::sleep(std::time::Duration::from_millis(100)); WindowOperationResult::Created } else { + logging!(warn, Type::Window, true, "窗口创建失败"); WindowOperationResult::Failed } } @@ -91,6 +149,16 @@ impl WindowManager { } WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => { if let Some(window) = Self::get_main_window() { + let state_after_check = Self::get_main_window_state(); + if state_after_check == WindowState::VisibleFocused { + logging!( + info, + Type::Window, + true, + "窗口在检查期间已变为可见和有焦点状态" + ); + return WindowOperationResult::NoAction; + } Self::activate_window(&window) } else { WindowOperationResult::Failed @@ -101,6 +169,14 @@ impl WindowManager { /// 切换主窗口显示状态(显示/隐藏) pub fn toggle_main_window() -> WindowOperationResult { + // 防抖检查 + if !should_handle_window_operation() { + return WindowOperationResult::NoAction; + } + let _guard = scopeguard::guard((), |_| { + finish_window_operation(); + }); + logging!(info, Type::Window, true, "开始切换主窗口显示状态"); let current_state = Self::get_main_window_state(); @@ -108,37 +184,61 @@ impl WindowManager { info, Type::Window, true, - "当前窗口状态: {:?}", - current_state + "当前窗口状态: {:?} | 详细状态: {}", + current_state, + Self::get_window_status_info() ); match current_state { WindowState::NotExist => { // 窗口不存在,创建新窗口 + logging!(info, Type::Window, true, "窗口不存在,将创建新窗口"); + // 由于已经有防抖保护,直接调用内部方法 if Self::create_new_window() { WindowOperationResult::Created } else { WindowOperationResult::Failed } } - WindowState::VisibleFocused => { - // 窗口可见且有焦点,隐藏它 - if let Some(window) = Self::get_main_window() { - if window.hide().is_ok() { - logging!(info, Type::Window, true, "窗口已隐藏"); - WindowOperationResult::Hidden + WindowState::VisibleFocused | WindowState::VisibleUnfocused => { + logging!( + info, + Type::Window, + true, + "窗口可见(焦点状态: {}),将隐藏窗口", + if current_state == WindowState::VisibleFocused { + "有焦点" } else { - WindowOperationResult::Failed + "无焦点" + } + ); + if let Some(window) = Self::get_main_window() { + match window.hide() { + Ok(_) => { + logging!(info, Type::Window, true, "窗口已成功隐藏"); + WindowOperationResult::Hidden + } + Err(e) => { + logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e); + WindowOperationResult::Failed + } } } else { + logging!(warn, Type::Window, true, "无法获取窗口实例"); WindowOperationResult::Failed } } - WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => { - // 窗口存在但不可见或无焦点,激活它 + WindowState::Minimized | WindowState::Hidden => { + logging!( + info, + Type::Window, + true, + "窗口存在但被隐藏或最小化,将激活窗口" + ); if let Some(window) = Self::get_main_window() { Self::activate_window(&window) } else { + logging!(warn, Type::Window, true, "无法获取窗口实例"); WindowOperationResult::Failed } } @@ -251,7 +351,7 @@ impl WindowManager { .unwrap_or(false) } - /// 创建新窗口现有的实现 + /// 创建新窗口,防抖避免重复调用 fn create_new_window() -> bool { use crate::utils::resolve; resolve::create_window(true) From e7461fccabe35c4014be7bf63b672e0924cd7652 Mon Sep 17 00:00:00 2001 From: Tunglies Date: Sun, 22 Jun 2025 16:28:06 +0800 Subject: [PATCH 03/15] refactor: remove unused macOS tray speed display and improve tray icon handling (#3862) * refactor: remove unused macOS tray speed display and improve tray icon handling * refactor: disable macOS specific logging during core initialization * feat: add linux elevator function to determine privilege escalation command --- UPDATELOG.md | 4 + src-tauri/src/core/core.rs | 7 +- src-tauri/src/core/tray/mod.rs | 216 +---------- src-tauri/src/core/tray/speed_rate.rs | 335 ------------------ src-tauri/src/module/mihomo.rs | 30 +- src-tauri/src/utils/help.rs | 49 --- src/components/setting/mods/layout-viewer.tsx | 4 +- 7 files changed, 14 insertions(+), 631 deletions(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index 973785cc11..5472ddc2d9 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -16,6 +16,10 @@ - 优化重构订阅切换逻辑,可以随时中断载入过程,防止卡死 - 引入事件驱动代理管理器,优化代理配置更新逻辑,防止卡死 +### 🗑️ 移除内容 + +- 移除了 macOS tray 图标显示网络速率 + ## v2.3.1 ### 🐞 修复问题 diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index c1d7e2e80b..bc2d54d1aa 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,5 +1,3 @@ -#[cfg(target_os = "macos")] -use crate::core::tray::Tray; use crate::{ config::*, core::{ @@ -980,9 +978,8 @@ impl CoreManager { } logging!(trace, Type::Core, "Initied core logic completed"); - #[cfg(target_os = "macos")] - logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await); - + // #[cfg(target_os = "macos")] + // logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await); Ok(()) } diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 98c46004e1..0fb4334400 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -12,15 +12,7 @@ use crate::{ }; use anyhow::Result; -#[cfg(target_os = "macos")] -use futures::StreamExt; use parking_lot::Mutex; -#[cfg(target_os = "macos")] -use parking_lot::RwLock; -#[cfg(target_os = "macos")] -pub use speed_rate::{SpeedRate, Traffic}; -#[cfg(target_os = "macos")] -use std::sync::Arc; use std::{ fs, sync::atomic::{AtomicBool, Ordering}, @@ -31,8 +23,6 @@ use tauri::{ tray::{MouseButton, MouseButtonState, TrayIconEvent}, AppHandle, Wry, }; -#[cfg(target_os = "macos")] -use tokio::sync::broadcast; use super::handle; @@ -64,10 +54,6 @@ fn should_handle_tray_click() -> bool { #[cfg(target_os = "macos")] pub struct Tray { - pub speed_rate: Arc>>, - shutdown_tx: Arc>>>, - is_subscribed: Arc>, - pub rate_cache: Arc>>, last_menu_update: Mutex>, menu_updating: AtomicBool, } @@ -187,10 +173,6 @@ impl Tray { #[cfg(target_os = "macos")] return TRAY.get_or_init(|| Tray { - speed_rate: Arc::new(Mutex::new(None)), - shutdown_tx: Arc::new(RwLock::new(None)), - is_subscribed: Arc::new(RwLock::new(false)), - rate_cache: Arc::new(Mutex::new(None)), last_menu_update: Mutex::new(None), menu_updating: AtomicBool::new(false), }); @@ -203,11 +185,6 @@ impl Tray { } pub fn init(&self) -> Result<()> { - #[cfg(target_os = "macos")] - { - let mut speed_rate = self.speed_rate.lock(); - *speed_rate = Some(SpeedRate::new()); - } Ok(()) } @@ -314,7 +291,7 @@ impl Tray { /// 更新托盘图标 #[cfg(target_os = "macos")] - pub fn update_icon(&self, rate: Option) -> Result<()> { + pub fn update_icon(&self, _rate: Option) -> Result<()> { let app_handle = match handle::Handle::global().app_handle() { Some(handle) => handle, None => { @@ -335,55 +312,18 @@ impl Tray { let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); - let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) { + let (_is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) { (true, true) => TrayState::get_tun_tray_icon(), (true, false) => TrayState::get_sysproxy_tray_icon(), (false, true) => TrayState::get_tun_tray_icon(), (false, false) => TrayState::get_common_tray_icon(), }; - let enable_tray_speed = verge.enable_tray_speed.unwrap_or(false); - let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true); let colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string()); let is_colorful = colorful == "colorful"; - if !enable_tray_speed { - let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?)); - let _ = tray.set_icon_as_template(!is_colorful); - return Ok(()); - } - - let rate = if let Some(rate) = rate { - Some(rate) - } else { - let guard = self.speed_rate.lock(); - if let Some(guard) = guard.as_ref() { - if let Some(rate) = guard.get_curent_rate() { - Some(rate) - } else { - Some(Rate::default()) - } - } else { - Some(Rate::default()) - } - }; - - let mut rate_guard = self.rate_cache.lock(); - if *rate_guard != rate { - *rate_guard = rate; - - let bytes = if enable_tray_icon { - Some(icon_bytes) - } else { - None - }; - - let rate = rate_guard.as_ref(); - if let Ok(rate_bytes) = SpeedRate::add_speed_text(is_custom_icon, bytes, rate) { - let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?)); - let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful); - } - } + let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?)); + let _ = tray.set_icon_as_template(!is_colorful); Ok(()) } @@ -498,155 +438,9 @@ impl Tray { Ok(()) } - /// 订阅流量数据 - #[cfg(target_os = "macos")] - pub async fn subscribe_traffic(&self) -> Result<()> { - log::info!(target: "app", "subscribe traffic"); - - // 如果已经订阅,先取消订阅 - if *self.is_subscribed.read() { - self.unsubscribe_traffic(); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - - let (shutdown_tx, shutdown_rx) = broadcast::channel(3); - *self.shutdown_tx.write() = Some(shutdown_tx); - *self.is_subscribed.write() = true; - - let speed_rate = Arc::clone(&self.speed_rate); - let is_subscribed = Arc::clone(&self.is_subscribed); - - // 使用单线程防止阻塞主线程 - std::thread::Builder::new() - .name("traffic-monitor".into()) - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("Failed to build tokio runtime for traffic monitor"); - // 在单独的运行时中执行异步任务 - rt.block_on(async move { - let mut shutdown = shutdown_rx; - let speed_rate = speed_rate.clone(); - let is_subscribed = is_subscribed.clone(); - let mut consecutive_errors = 0; - let max_consecutive_errors = 5; - - let mut interval = tokio::time::interval(std::time::Duration::from_secs(10)); - - 'outer: loop { - if !*is_subscribed.read() { - log::info!(target: "app", "Traffic subscription has been cancelled"); - break; - } - - match tokio::time::timeout( - std::time::Duration::from_secs(5), - Traffic::get_traffic_stream() - ).await { - Ok(stream_result) => { - match stream_result { - Ok(mut stream) => { - consecutive_errors = 0; - - loop { - tokio::select! { - traffic_result = stream.next() => { - match traffic_result { - Some(Ok(traffic)) => { - if let Ok(Some(rate)) = tokio::time::timeout( - std::time::Duration::from_millis(50), - async { - let guard = speed_rate.try_lock(); - if let Some(guard) = guard { - if let Some(sr) = guard.as_ref() { - sr.update_and_check_changed(traffic.up, traffic.down) - } else { - None - } - } else { - None - } - } - ).await { - let _ = tokio::time::timeout( - std::time::Duration::from_millis(100), - async { let _ = Tray::global().update_icon(Some(rate)); } - ).await; - } - }, - Some(Err(e)) => { - log::error!(target: "app", "Traffic stream error: {}", e); - consecutive_errors += 1; - if consecutive_errors >= max_consecutive_errors { - log::error!(target: "app", "Too many errors, reconnecting traffic stream"); - break; - } - }, - None => { - log::info!(target: "app", "Traffic stream ended, reconnecting"); - break; - } - } - }, - _ = shutdown.recv() => { - log::info!(target: "app", "Received shutdown signal for traffic stream"); - break 'outer; - }, - _ = interval.tick() => { - if !*is_subscribed.read() { - log::info!(target: "app", "Traffic monitor detected subscription cancelled"); - break 'outer; - } - log::debug!(target: "app", "Traffic subscription periodic health check"); - }, - _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { - log::info!(target: "app", "Traffic stream max active time reached, reconnecting"); - break; - } - } - } - }, - Err(e) => { - log::error!(target: "app", "Failed to get traffic stream: {}", e); - consecutive_errors += 1; - if consecutive_errors >= max_consecutive_errors { - log::error!(target: "app", "Too many consecutive errors, pausing traffic monitoring"); - tokio::time::sleep(std::time::Duration::from_secs(30)).await; - consecutive_errors = 0; - } else { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - } - } - }, - Err(_) => { - log::error!(target: "app", "Traffic stream initialization timed out"); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - } - - if !*is_subscribed.read() { - break; - } - } - log::info!(target: "app", "Traffic subscription thread terminated"); - }); - }) - .expect("Failed to spawn traffic monitor thread"); - - Ok(()) - } - /// 取消订阅 traffic 数据 #[cfg(target_os = "macos")] - pub fn unsubscribe_traffic(&self) { - log::info!(target: "app", "unsubscribe traffic"); - *self.is_subscribed.write() = false; - if let Some(tx) = self.shutdown_tx.write().take() { - drop(tx); - } - } + pub fn unsubscribe_traffic(&self) {} pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> { log::info!(target: "app", "正在从AppHandle创建系统托盘"); diff --git a/src-tauri/src/core/tray/speed_rate.rs b/src-tauri/src/core/tray/speed_rate.rs index 06cbf61014..8b13789179 100644 --- a/src-tauri/src/core/tray/speed_rate.rs +++ b/src-tauri/src/core/tray/speed_rate.rs @@ -1,336 +1 @@ -use crate::{ - module::mihomo::{MihomoManager, Rate}, - utils::help::format_bytes_speed, -}; -use ab_glyph::FontArc; -use anyhow::Result; -use futures::Stream; -use image::{GenericImageView, Rgba, RgbaImage}; -use imageproc::drawing::draw_text_mut; -use parking_lot::Mutex; -use std::{io::Cursor, sync::Arc}; -use tokio_tungstenite::tungstenite::http; -use tungstenite::client::IntoClientRequest; -#[derive(Debug, Clone)] -pub struct SpeedRate { - rate: Arc>, - last_update: Arc>, -} -impl SpeedRate { - pub fn new() -> Self { - Self { - rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))), - last_update: Arc::new(Mutex::new(std::time::Instant::now())), - } - } - - pub fn update_and_check_changed(&self, up: u64, down: u64) -> Option { - let mut rates = self.rate.lock(); - let mut last_update = self.last_update.lock(); - let now = std::time::Instant::now(); - - // 限制更新频率为每秒最多2次(500ms) - if now.duration_since(*last_update).as_millis() < 500 { - return None; - } - - let (current, previous) = &mut *rates; - - // Avoid unnecessary float conversions for small value checks - let should_update = if current.up < 1000 && down < 1000 { - // For small values, always update to ensure accuracy - current.up != up || current.down != down - } else { - // For larger values, use integer math to check for >5% change - // Multiply by 20 instead of dividing by 0.05 to avoid floating point - let up_threshold = current.up / 20; - let down_threshold = current.down / 20; - - (up > current.up && up - current.up > up_threshold) - || (up < current.up && current.up - up > up_threshold) - || (down > current.down && down - current.down > down_threshold) - || (down < current.down && current.down - down > down_threshold) - }; - - if !should_update { - return None; - } - - *previous = current.clone(); - current.up = up; - current.down = down; - *last_update = now; - - if previous != current { - Some(current.clone()) - } else { - None - } - } - - pub fn get_curent_rate(&self) -> Option { - let rates = self.rate.lock(); - let (current, _) = &*rates; - Some(current.clone()) - } - - // 分离图标加载和速率渲染 - pub fn add_speed_text( - is_custom_icon: bool, - icon_bytes: Option>, - rate: Option<&Rate>, - ) -> Result> { - let rate = rate.unwrap_or(&Rate { up: 0, down: 0 }); - - let (mut icon_width, mut icon_height) = (0, 256); - let icon_image = if let Some(bytes) = icon_bytes.clone() { - let icon_image = image::load_from_memory(&bytes)?; - icon_width = icon_image.width(); - icon_height = icon_image.height(); - icon_image - } else { - // 返回一个空的 RGBA 图像 - image::DynamicImage::new_rgba8(0, 0) - }; - - let total_width = match (is_custom_icon, icon_bytes.is_some()) { - (true, true) => 510, - (true, false) => 740, - (false, false) => 740, - (false, true) => icon_width + 740, - }; - - // println!( - // "icon_height: {}, icon_wight: {}, total_width: {}", - // icon_height, icon_width, total_width - // ); - - // 创建新的透明画布 - let mut combined_image = RgbaImage::new(total_width, icon_height); - - // 将原始图标绘制到新画布的左侧 - if icon_bytes.is_some() { - for y in 0..icon_height { - for x in 0..icon_width { - let pixel = icon_image.get_pixel(x, y); - combined_image.put_pixel(x, y, pixel); - } - } - } - - let is_colorful = if let Some(bytes) = icon_bytes.clone() { - !crate::utils::help::is_monochrome_image_from_bytes(&bytes).unwrap_or(false) - } else { - false - }; - - // 选择文本颜色 - let (text_color, shadow_color) = if is_colorful { - ( - Rgba([144u8, 144u8, 144u8, 255u8]), - // Rgba([255u8, 255u8, 255u8, 128u8]), - Rgba([0u8, 0u8, 0u8, 128u8]), - ) - // ( - // Rgba([160u8, 160u8, 160u8, 255u8]), - // // Rgba([255u8, 255u8, 255u8, 128u8]), - // Rgba([0u8, 0u8, 0u8, 255u8]), - // ) - } else { - ( - Rgba([255u8, 255u8, 255u8, 255u8]), - Rgba([0u8, 0u8, 0u8, 128u8]), - ) - }; - // 减小字体大小以适应文本区域 - let font_data = include_bytes!("../../../assets/fonts/SF-Pro.ttf"); - let font = FontArc::try_from_vec(font_data.to_vec()).unwrap(); - let font_size = icon_height as f32 * 0.6; // 稍微减小字体 - let scale = ab_glyph::PxScale::from(font_size); - - // 使用更简洁的速率格式 - let up_text = format!("↑ {}", format_bytes_speed(rate.up)); - let down_text = format!("↓ {}", format_bytes_speed(rate.down)); - - // For test rate display - // let down_text = format!("↓ {}", format_bytes_speed(102 * 1020 * 1024)); - - // 计算文本位置,确保垂直间距合适 - // 修改文本位置为居右显示 - // 计算右对齐的文本位置 - // let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32; - // let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32; - // let up_text_x = total_width - up_text_width; - // let down_text_x = total_width - down_text_width; - - // 计算左对齐的文本位置 - let (up_text_x, down_text_x) = { - if is_custom_icon || icon_bytes.is_some() { - let text_left_offset = 30; - let left_begin = icon_width + text_left_offset; - (left_begin, left_begin) - } else { - (icon_width, icon_width) - } - }; - - // 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小 - let text_height = font_size as i32; - let total_text_height = text_height * 2; - let up_y = (icon_height as i32 - total_text_height) / 2; - let down_y = up_y + text_height; - - // 绘制速率文本(先阴影后文字) - let shadow_offset = 1; - - // 绘制上行速率 - draw_text_mut( - &mut combined_image, - shadow_color, - up_text_x as i32 + shadow_offset, - up_y + shadow_offset, - scale, - &font, - &up_text, - ); - draw_text_mut( - &mut combined_image, - text_color, - up_text_x as i32, - up_y, - scale, - &font, - &up_text, - ); - - // 绘制下行速率 - draw_text_mut( - &mut combined_image, - shadow_color, - down_text_x as i32 + shadow_offset, - down_y + shadow_offset, - scale, - &font, - &down_text, - ); - draw_text_mut( - &mut combined_image, - text_color, - down_text_x as i32, - down_y, - scale, - &font, - &down_text, - ); - - // 将结果转换为 PNG 数据 - let mut bytes = Vec::new(); - combined_image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?; - Ok(bytes) - } -} - -#[derive(Debug, Clone)] -pub struct Traffic { - pub up: u64, - pub down: u64, -} - -impl Traffic { - pub async fn get_traffic_stream() -> Result>> - { - use futures::{ - future::FutureExt, - stream::{self, StreamExt}, - }; - use std::time::Duration; - - // 先处理错误和超时情况 - let stream = Box::pin( - stream::unfold((), move |_| async move { - 'retry: loop { - log::info!(target: "app", "establishing traffic websocket connection"); - let (url, token) = MihomoManager::get_traffic_ws_url(); - let mut request = match url.into_client_request() { - Ok(req) => req, - Err(e) => { - log::error!(target: "app", "failed to create websocket request: {}", e); - tokio::time::sleep(Duration::from_secs(2)).await; - continue 'retry; - } - }; - - request.headers_mut().insert(http::header::AUTHORIZATION, token); - - match tokio::time::timeout(Duration::from_secs(3), - tokio_tungstenite::connect_async(request) - ).await { - Ok(Ok((ws_stream, _))) => { - log::info!(target: "app", "traffic websocket connection established"); - // 设置流超时控制 - let traffic_stream = ws_stream - .take_while(|msg| { - let continue_stream = msg.is_ok(); - async move { continue_stream }.boxed() - }) - .filter_map(|msg| async move { - match msg { - Ok(msg) => { - if !msg.is_text() { - return None; - } - - match tokio::time::timeout( - Duration::from_millis(200), - async { msg.into_text() } - ).await { - Ok(Ok(text)) => { - match serde_json::from_str::(&text) { - Ok(json) => { - let up = json["up"].as_u64().unwrap_or(0); - let down = json["down"].as_u64().unwrap_or(0); - Some(Ok(Traffic { up, down })) - }, - Err(e) => { - log::warn!(target: "app", "traffic json parse error: {} for {}", e, text); - None - } - } - }, - Ok(Err(e)) => { - log::warn!(target: "app", "traffic text conversion error: {}", e); - None - }, - Err(_) => { - log::warn!(target: "app", "traffic text processing timeout"); - None - } - } - }, - Err(e) => { - log::error!(target: "app", "traffic websocket error: {}", e); - None - } - } - }); - - return Some((traffic_stream, ())); - }, - Ok(Err(e)) => { - log::error!(target: "app", "traffic websocket connection failed: {}", e); - }, - Err(_) => { - log::error!(target: "app", "traffic websocket connection timed out"); - } - } - - tokio::time::sleep(Duration::from_secs(2)).await; - } - }) - .flatten(), - ); - - Ok(stream) - } -} diff --git a/src-tauri/src/module/mihomo.rs b/src-tauri/src/module/mihomo.rs index 0fc40d31f3..37eb57a752 100644 --- a/src-tauri/src/module/mihomo.rs +++ b/src-tauri/src/module/mihomo.rs @@ -4,8 +4,6 @@ use once_cell::sync::Lazy; use parking_lot::{Mutex, RwLock}; use std::time::{Duration, Instant}; use tauri::http::HeaderMap; -#[cfg(target_os = "macos")] -use tauri::http::HeaderValue; // 缓存的最大有效期(5秒) const CACHE_TTL: Duration = Duration::from_secs(5); @@ -106,31 +104,5 @@ impl MihomoManager { Some((server, headers)) } - // 提供默认值的版本,避免在connection_info为None时panic - #[cfg(target_os = "macos")] - fn get_clash_client_info_or_default() -> (String, HeaderMap) { - Self::get_clash_client_info().unwrap_or_else(|| { - let mut headers = HeaderMap::new(); - headers.insert("Content-Type", "application/json".parse().unwrap()); - ("http://127.0.0.1:9090".to_string(), headers) - }) - } - - #[cfg(target_os = "macos")] - pub fn get_traffic_ws_url() -> (String, HeaderValue) { - let (url, headers) = MihomoManager::get_clash_client_info_or_default(); - let ws_url = url.replace("http://", "ws://") + "/traffic"; - let auth = headers - .get("Authorization") - .map(|val| val.to_str().unwrap_or("").to_string()) - .unwrap_or_default(); - - // 创建默认的空HeaderValue而不是使用unwrap_or_default - let token = match HeaderValue::from_str(&auth) { - Ok(v) => v, - Err(_) => HeaderValue::from_static(""), - }; - - (ws_url, token) - } + // 已移除未使用的 get_clash_client_info_or_default 和 get_traffic_ws_url 方法 } diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 0df0573bee..4c30b0f1a8 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -125,19 +125,6 @@ pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> { Ok(()) } -#[cfg(target_os = "macos")] -pub fn is_monochrome_image_from_bytes(data: &[u8]) -> anyhow::Result { - let img = image::load_from_memory(data)?; - let rgb_img = img.to_rgb8(); - - for pixel in rgb_img.pixels() { - if pixel[0] != pixel[1] || pixel[1] != pixel[2] { - return Ok(false); - } - } - Ok(true) -} - #[cfg(target_os = "linux")] pub fn linux_elevator() -> String { use std::process::Command; @@ -176,39 +163,3 @@ macro_rules! t { } }; } - -/// 将字节数转换为可读的流量字符串 -/// 支持 B/s、KB/s、MB/s、GB/s 的自动转换 -/// -/// # Examples -/// ```not_run -/// format_bytes_speed(1000) // returns "1000B/s" -/// format_bytes_speed(1024) // returns "1.0KB/s" -/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s" -/// ``` -/// ``` -#[cfg(target_os = "macos")] -pub fn format_bytes_speed(speed: u64) -> String { - const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"]; - let mut size = speed as f64; - let mut unit_index = 0; - - while size >= 1000.0 && unit_index < UNITS.len() - 1 { - size /= 1024.0; - unit_index += 1; - } - - format!("{:.1}{}/s", size, UNITS[unit_index]) -} - -#[cfg(target_os = "macos")] -#[test] -fn test_format_bytes_speed() { - assert_eq!(format_bytes_speed(0), "0.0B/s"); - assert_eq!(format_bytes_speed(1023), "1.0KB/s"); - assert_eq!(format_bytes_speed(1024), "1.0KB/s"); - assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s"); - assert_eq!(format_bytes_speed(1024 * 1024 * 1024), "1.0GB/s"); - assert_eq!(format_bytes_speed(1024 * 500), "500.0KB/s"); - assert_eq!(format_bytes_speed(1024 * 1024 * 2), "2.0MB/s"); -} diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx index 55425e459c..2b498b76ee 100644 --- a/src/components/setting/mods/layout-viewer.tsx +++ b/src/components/setting/mods/layout-viewer.tsx @@ -209,7 +209,7 @@ export const LayoutViewer = forwardRef((props, ref) => { )} - {OS === "macos" && ( + {/* {OS === "macos" && ( ((props, ref) => { - )} + )} */} {OS === "macos" && ( From f6b5524e0e25d2c6217b5d01305e396a5d537a3f Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 22 Jun 2025 18:45:38 +0800 Subject: [PATCH 04/15] refactor: replace shell command check with WinAPI call --- src-tauri/Cargo.toml | 2 + src-tauri/src/core/core.rs | 220 +++++++++++++++++++++++-------------- 2 files changed, 138 insertions(+), 84 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 843ce20514..7b04a3b4e1 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -94,6 +94,8 @@ winapi = { version = "0.3.9", features = [ "errhandlingapi", "minwindef", "winerror", + "tlhelp32", + "processthreadsapi", ] } [target.'cfg(target_os = "linux")'.dependencies] diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index bc2d54d1aa..4c4589823d 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -514,70 +514,102 @@ impl CoreManager { Ok(()) } - /// 根据进程名查找进程PID列表 + /// 根据进程名查找进程PID列 async fn find_processes_by_name( &self, process_name: String, _target: &str, ) -> Result<(Vec, String)> { - let output = if cfg!(windows) { - tokio::process::Command::new("tasklist") - .args([ - "/FI", - &format!("IMAGENAME eq {}", process_name), - "/FO", - "CSV", - "/NH", - ]) - .output() - .await? - } else if cfg!(target_os = "macos") { - tokio::process::Command::new("pgrep") - .arg(&process_name) - .output() - .await? - } else { - // Linux - tokio::process::Command::new("pidof") - .arg(&process_name) - .output() - .await? - }; + #[cfg(windows)] + { + use std::mem; + use winapi::um::handleapi::CloseHandle; + use winapi::um::tlhelp32::{ + CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, + TH32CS_SNAPPROCESS, + }; + use winapi::um::winnt::HANDLE; - if !output.status.success() { - return Ok((Vec::new(), process_name)); - } + let process_name_clone = process_name.clone(); + let pids = tokio::task::spawn_blocking(move || -> Result> { + let mut pids = Vec::new(); - let stdout = String::from_utf8_lossy(&output.stdout); - let mut pids = Vec::new(); - - if cfg!(windows) { - // 解析CSV格式输出: "进程名","PID","会话名","会话#","内存使用" - for line in stdout.lines() { - if !line.is_empty() && line.contains(&process_name) { - let fields: Vec<&str> = line.split(',').collect(); - if fields.len() >= 2 { - // 移除引号并解析PID - let pid_str = fields[1].trim_matches('"'); - if let Ok(pid) = pid_str.parse::() { - pids.push(pid); + unsafe { + // 创建进程快照 + let snapshot: HANDLE = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if snapshot == winapi::um::handleapi::INVALID_HANDLE_VALUE { + return Err(anyhow::anyhow!("Failed to create process snapshot")); + } + + let mut pe32: PROCESSENTRY32W = mem::zeroed(); + pe32.dwSize = mem::size_of::() as u32; + + // 获取第一个进程 + if Process32FirstW(snapshot, &mut pe32) != 0 { + loop { + // 将宽字符转换为String + let end_pos = pe32 + .szExeFile + .iter() + .position(|&x| x == 0) + .unwrap_or(pe32.szExeFile.len()); + let exe_file = String::from_utf16_lossy(&pe32.szExeFile[..end_pos]); + + // 检查进程名是否匹配 + if exe_file.eq_ignore_ascii_case(&process_name_clone) { + pids.push(pe32.th32ProcessID); + } + if Process32NextW(snapshot, &mut pe32) == 0 { + break; + } } } + + // 关闭句柄 + CloseHandle(snapshot); } + + Ok(pids) + }) + .await??; + + Ok((pids, process_name)) + } + + #[cfg(not(windows))] + { + let output = if cfg!(target_os = "macos") { + tokio::process::Command::new("pgrep") + .arg(&process_name) + .output() + .await? + } else { + // Linux + tokio::process::Command::new("pidof") + .arg(&process_name) + .output() + .await? + }; + + if !output.status.success() { + return Ok((Vec::new(), process_name)); } - } else { + + let stdout = String::from_utf8_lossy(&output.stdout); + let mut pids = Vec::new(); + // Unix系统直接解析PID列表 for pid_str in stdout.split_whitespace() { if let Ok(pid) = pid_str.parse::() { pids.push(pid); } } - } - Ok((pids, process_name)) + Ok((pids, process_name)) + } } - /// 终止进程并验证结果 + /// 终止进程并验证结果 - 使用Windows API直接终止,更优雅高效 async fn kill_process_with_verification(&self, pid: u32, process_name: String) -> bool { logging!( info, @@ -588,14 +620,30 @@ impl CoreManager { pid ); - let success = if cfg!(windows) { - tokio::process::Command::new("taskkill") - .args(["/F", "/PID", &pid.to_string()]) - .output() - .await - .map(|output| output.status.success()) - .unwrap_or(false) - } else { + #[cfg(windows)] + let success = { + use winapi::um::handleapi::CloseHandle; + use winapi::um::processthreadsapi::{OpenProcess, TerminateProcess}; + use winapi::um::winnt::{HANDLE, PROCESS_TERMINATE}; + + tokio::task::spawn_blocking(move || -> bool { + unsafe { + let process_handle: HANDLE = OpenProcess(PROCESS_TERMINATE, 0, pid); + if process_handle.is_null() { + return false; + } + let result = TerminateProcess(process_handle, 1); + CloseHandle(process_handle); + + result != 0 + } + }) + .await + .unwrap_or(false) + }; + + #[cfg(not(windows))] + let success = { tokio::process::Command::new("kill") .args(["-9", &pid.to_string()]) .output() @@ -643,34 +691,49 @@ impl CoreManager { } } - /// 检查进程是否仍在运行 + /// Windows API检查进程 async fn is_process_running(&self, pid: u32) -> Result { - let output = if cfg!(windows) { - tokio::process::Command::new("tasklist") - .args(["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"]) - .output() - .await? - } else { - tokio::process::Command::new("ps") + #[cfg(windows)] + { + use winapi::shared::minwindef::DWORD; + use winapi::um::handleapi::CloseHandle; + use winapi::um::processthreadsapi::GetExitCodeProcess; + use winapi::um::processthreadsapi::OpenProcess; + use winapi::um::winnt::{HANDLE, PROCESS_QUERY_INFORMATION}; + + let result = tokio::task::spawn_blocking(move || -> Result { + unsafe { + let process_handle: HANDLE = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid); + if process_handle.is_null() { + return Ok(false); + } + let mut exit_code: DWORD = 0; + let result = GetExitCodeProcess(process_handle, &mut exit_code); + CloseHandle(process_handle); + + if result == 0 { + return Ok(false); + } + Ok(exit_code == 259) + } + }) + .await?; + + result + } + + #[cfg(not(windows))] + { + let output = tokio::process::Command::new("ps") .args(["-p", &pid.to_string()]) .output() - .await? - }; + .await?; - Ok(output.status.success() && !output.stdout.is_empty()) + Ok(output.status.success() && !output.stdout.is_empty()) + } } async fn start_core_by_sidecar(&self) -> Result<()> { - if let Err(e) = self.cleanup_orphaned_mihomo_processes().await { - logging!( - warn, - Type::Core, - true, - "清理多余 mihomo 进程时发生错误: {}", - e - ); - } - logging!(trace, Type::Core, true, "Running core by sidecar"); let config_file = &Config::generate_file(ConfigType::Run)?; let app_handle = handle::Handle::global() @@ -1033,17 +1096,6 @@ impl CoreManager { pub async fn restart_core(&self) -> Result<()> { self.stop_core().await?; - // 在重启时也清理多余的 mihomo 进程 - if let Err(e) = self.cleanup_orphaned_mihomo_processes().await { - logging!( - warn, - Type::Core, - true, - "重启时清理多余 mihomo 进程失败: {}", - e - ); - } - self.start_core().await?; Ok(()) } From bdfc383a1858f61f30bec588c9c04bb3940dc15d Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 22 Jun 2025 21:05:50 +0800 Subject: [PATCH 05/15] refactor: enhance Windows proxy retrieval by using WinAPI for registry access --- src-tauri/Cargo.toml | 2 + src-tauri/src/core/async_proxy_query.rs | 288 ++++++++++++++++------- src-tauri/src/core/event_driven_proxy.rs | 25 +- src-tauri/src/utils/autostart.rs | 4 +- 4 files changed, 215 insertions(+), 104 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7b04a3b4e1..cd95b11b81 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -96,6 +96,8 @@ winapi = { version = "0.3.9", features = [ "winerror", "tlhelp32", "processthreadsapi", + "winhttp", + "winreg", ] } [target.'cfg(target_os = "linux")'.dependencies] diff --git a/src-tauri/src/core/async_proxy_query.rs b/src-tauri/src/core/async_proxy_query.rs index b26a4951eb..5fabaf217d 100644 --- a/src-tauri/src/core/async_proxy_query.rs +++ b/src-tauri/src/core/async_proxy_query.rs @@ -1,10 +1,12 @@ -#[cfg(target_os = "linux")] -use anyhow::anyhow; use anyhow::Result; use serde::{Deserialize, Serialize}; -use tokio::process::Command; use tokio::time::{timeout, Duration}; +#[cfg(target_os = "linux")] +use anyhow::anyhow; +#[cfg(not(target_os = "windows"))] +use tokio::process::Command; + #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AsyncAutoproxy { pub enable: bool, @@ -71,54 +73,96 @@ impl AsyncProxyQuery { #[cfg(target_os = "windows")] async fn get_auto_proxy_impl() -> Result { - // Windows: 使用 netsh winhttp show proxy 命令 - let output = Command::new("netsh") - .args(["winhttp", "show", "proxy"]) - .output() - .await?; + // Windows: 从注册表读取PAC配置 + tokio::task::spawn_blocking(move || -> Result { + Self::get_pac_config_from_registry() + }) + .await? + } - if !output.status.success() { - return Ok(AsyncAutoproxy::default()); - } + #[cfg(target_os = "windows")] + fn get_pac_config_from_registry() -> Result { + use std::ptr; + use winapi::shared::minwindef::{DWORD, HKEY}; + use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ}; + use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER}; + + unsafe { + let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0" + .encode_utf16() + .collect::>(); + + let mut hkey: HKEY = ptr::null_mut(); + let result = + RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey); + + if result != 0 { + log::debug!(target: "app", "无法打开注册表项"); + return Ok(AsyncAutoproxy::default()); + } - let stdout = String::from_utf8_lossy(&output.stdout); - log::debug!(target: "app", "netsh output: {}", stdout); + // 1. 检查自动配置是否启用 (AutoConfigURL 存在且不为空即表示启用) + let auto_config_url_name = "AutoConfigURL\0".encode_utf16().collect::>(); + let mut url_buffer = vec![0u16; 1024]; + let mut url_buffer_size: DWORD = (url_buffer.len() * 2) as DWORD; + let mut url_value_type: DWORD = 0; + + let url_query_result = RegQueryValueExW( + hkey, + auto_config_url_name.as_ptr(), + ptr::null_mut(), + &mut url_value_type, + url_buffer.as_mut_ptr() as *mut u8, + &mut url_buffer_size, + ); + + let mut pac_url = String::new(); + if url_query_result == 0 && url_value_type == REG_SZ && url_buffer_size > 0 { + let end_pos = url_buffer + .iter() + .position(|&x| x == 0) + .unwrap_or(url_buffer.len()); + pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]); + log::debug!(target: "app", "从注册表读取到PAC URL: {}", pac_url); + } - // 解析输出,查找 PAC 配置 - for line in stdout.lines() { - let line = line.trim(); - if line.starts_with("代理自动配置脚本") || line.starts_with("Proxy auto-config script") - { - // 修复:正确解析包含冒号的URL - // 格式: "代理自动配置脚本 : http://127.0.0.1:11233/commands/pac" - // 或: "Proxy auto-config script : http://127.0.0.1:11233/commands/pac" - if let Some(colon_pos) = line.find(" : ") { - let url = line[colon_pos + 3..].trim(); - if !url.is_empty() && url != "(none)" && url != "无" { - log::debug!(target: "app", "解析到PAC URL: {}", url); - return Ok(AsyncAutoproxy { - enable: true, - url: url.to_string(), - }); - } - } else if let Some(colon_pos) = line.find(':') { - // 兼容其他可能的格式 - let url = line[colon_pos + 1..].trim(); - // 确保这不是URL中的协议部分 - if url.starts_with("http") && !url.is_empty() && url != "(none)" && url != "无" - { - log::debug!(target: "app", "解析到PAC URL (fallback): {}", url); - return Ok(AsyncAutoproxy { - enable: true, - url: url.to_string(), - }); - } + // 2. 检查自动检测设置是否启用 + let auto_detect_name = "AutoDetect\0".encode_utf16().collect::>(); + let mut auto_detect: DWORD = 0; + let mut detect_buffer_size: DWORD = 4; + let mut detect_value_type: DWORD = 0; + + let detect_query_result = RegQueryValueExW( + hkey, + auto_detect_name.as_ptr(), + ptr::null_mut(), + &mut detect_value_type, + &mut auto_detect as *mut DWORD as *mut u8, + &mut detect_buffer_size, + ); + + RegCloseKey(hkey); + + // PAC 启用的条件:AutoConfigURL 不为空,或 AutoDetect 被启用 + let pac_enabled = !pac_url.is_empty() + || (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0); + + if pac_enabled { + log::debug!(target: "app", "PAC配置启用: URL={}, AutoDetect={}", pac_url, auto_detect); + + if pac_url.is_empty() && auto_detect != 0 { + pac_url = "auto-detect".to_string(); } + + Ok(AsyncAutoproxy { + enable: true, + url: pac_url, + }) + } else { + log::debug!(target: "app", "PAC配置未启用"); + Ok(AsyncAutoproxy::default()) } } - - log::debug!(target: "app", "未找到有效的PAC配置"); - Ok(AsyncAutoproxy::default()) } #[cfg(target_os = "macos")] @@ -142,7 +186,7 @@ impl AsyncProxyQuery { if line.contains("ProxyAutoConfigEnable") && line.contains("1") { pac_enabled = true; } else if line.contains("ProxyAutoConfigURLString") { - // 修复:正确解析包含冒号的URL + // 正确解析包含冒号的URL // 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac" if let Some(colon_pos) = line.find(" : ") { pac_url = line[colon_pos + 3..].trim().to_string(); @@ -213,57 +257,121 @@ impl AsyncProxyQuery { #[cfg(target_os = "windows")] async fn get_system_proxy_impl() -> Result { - let output = Command::new("netsh") - .args(["winhttp", "show", "proxy"]) - .output() - .await?; + // Windows: 使用注册表直接读取代理设置 + tokio::task::spawn_blocking(move || -> Result { + Self::get_system_proxy_from_registry() + }) + .await? + } - if !output.status.success() { - return Ok(AsyncSysproxy::default()); - } + #[cfg(target_os = "windows")] + fn get_system_proxy_from_registry() -> Result { + use std::ptr; + use winapi::shared::minwindef::{DWORD, HKEY}; + use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ}; + use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER}; + + unsafe { + let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0" + .encode_utf16() + .collect::>(); + + let mut hkey: HKEY = ptr::null_mut(); + let result = + RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey); + + if result != 0 { + return Ok(AsyncSysproxy::default()); + } - let stdout = String::from_utf8_lossy(&output.stdout); - log::debug!(target: "app", "netsh proxy output: {}", stdout); + // 检查代理是否启用 + let proxy_enable_name = "ProxyEnable\0".encode_utf16().collect::>(); + let mut proxy_enable: DWORD = 0; + let mut buffer_size: DWORD = 4; + let mut value_type: DWORD = 0; + + let enable_result = RegQueryValueExW( + hkey, + proxy_enable_name.as_ptr(), + ptr::null_mut(), + &mut value_type, + &mut proxy_enable as *mut DWORD as *mut u8, + &mut buffer_size, + ); + + if enable_result != 0 || value_type != REG_DWORD || proxy_enable == 0 { + RegCloseKey(hkey); + return Ok(AsyncSysproxy::default()); + } - let mut proxy_enabled = false; - let mut proxy_server = String::new(); - let mut bypass_list = String::new(); + // 读取代理服务器设置 + let proxy_server_name = "ProxyServer\0".encode_utf16().collect::>(); + let mut buffer = vec![0u16; 1024]; + let mut buffer_size: DWORD = (buffer.len() * 2) as DWORD; + let mut value_type: DWORD = 0; + + let server_result = RegQueryValueExW( + hkey, + proxy_server_name.as_ptr(), + ptr::null_mut(), + &mut value_type, + buffer.as_mut_ptr() as *mut u8, + &mut buffer_size, + ); + + let mut proxy_server = String::new(); + if server_result == 0 && value_type == REG_SZ && buffer_size > 0 { + let end_pos = buffer.iter().position(|&x| x == 0).unwrap_or(buffer.len()); + proxy_server = String::from_utf16_lossy(&buffer[..end_pos]); + } - for line in stdout.lines() { - let line = line.trim(); - if line.starts_with("代理服务器") || line.starts_with("Proxy Server") { - if let Some(server_part) = line.split(':').nth(1) { - let server = server_part.trim(); - if !server.is_empty() && server != "(none)" && server != "无" { - proxy_server = server.to_string(); - proxy_enabled = true; - } - } - } else if line.starts_with("绕过列表") || line.starts_with("Bypass List") { - if let Some(bypass_part) = line.split(':').nth(1) { - bypass_list = bypass_part.trim().to_string(); - } + // 读取代理绕过列表 + let proxy_override_name = "ProxyOverride\0".encode_utf16().collect::>(); + let mut bypass_buffer = vec![0u16; 1024]; + let mut bypass_buffer_size: DWORD = (bypass_buffer.len() * 2) as DWORD; + let mut bypass_value_type: DWORD = 0; + + let override_result = RegQueryValueExW( + hkey, + proxy_override_name.as_ptr(), + ptr::null_mut(), + &mut bypass_value_type, + bypass_buffer.as_mut_ptr() as *mut u8, + &mut bypass_buffer_size, + ); + + let mut bypass_list = String::new(); + if override_result == 0 && bypass_value_type == REG_SZ && bypass_buffer_size > 0 { + let end_pos = bypass_buffer + .iter() + .position(|&x| x == 0) + .unwrap_or(bypass_buffer.len()); + bypass_list = String::from_utf16_lossy(&bypass_buffer[..end_pos]); } - } - if proxy_enabled && !proxy_server.is_empty() { - // 解析服务器地址和端口 - let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') { - let host = proxy_server[..colon_pos].to_string(); - let port = proxy_server[colon_pos + 1..].parse::().unwrap_or(8080); - (host, port) + RegCloseKey(hkey); + + if !proxy_server.is_empty() { + // 解析服务器地址和端口 + let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') { + let host = proxy_server[..colon_pos].to_string(); + let port = proxy_server[colon_pos + 1..].parse::().unwrap_or(8080); + (host, port) + } else { + (proxy_server, 8080) + }; + + log::debug!(target: "app", "从注册表读取到代理设置: {}:{}, bypass: {}", host, port, bypass_list); + + Ok(AsyncSysproxy { + enable: true, + host, + port, + bypass: bypass_list, + }) } else { - (proxy_server, 8080) - }; - - Ok(AsyncSysproxy { - enable: true, - host, - port, - bypass: bypass_list, - }) - } else { - Ok(AsyncSysproxy::default()) + Ok(AsyncSysproxy::default()) + } } } diff --git a/src-tauri/src/core/event_driven_proxy.rs b/src-tauri/src/core/event_driven_proxy.rs index 6813dd1e06..09bbc165d9 100644 --- a/src-tauri/src/core/event_driven_proxy.rs +++ b/src-tauri/src/core/event_driven_proxy.rs @@ -526,16 +526,10 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn execute_sysproxy_command(args: &[&str]) { - use crate::{core::handle::Handle, utils::dirs}; - use tauri_plugin_shell::ShellExt; - - let app_handle = match Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::error!(target: "app", "获取应用句柄失败"); - return; - } - }; + use crate::utils::dirs; + #[allow(unused_imports)] // creation_flags必须 + use std::os::windows::process::CommandExt; + use tokio::process::Command; let binary_path = match dirs::service_path() { Ok(path) => path, @@ -551,10 +545,9 @@ impl EventDrivenProxyManager { return; } - let shell = app_handle.shell(); - let output = shell - .command(sysproxy_exe.as_path().to_str().unwrap()) + let output = Command::new(sysproxy_exe) .args(args) + .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口 .output() .await; @@ -562,6 +555,12 @@ impl EventDrivenProxyManager { Ok(output) => { if !output.status.success() { log::error!(target: "app", "执行sysproxy命令失败: {:?}", args); + let stderr = String::from_utf8_lossy(&output.stderr); + if !stderr.is_empty() { + log::error!(target: "app", "sysproxy错误输出: {}", stderr); + } + } else { + log::debug!(target: "app", "成功执行sysproxy命令: {:?}", args); } } Err(e) => { diff --git a/src-tauri/src/utils/autostart.rs b/src-tauri/src/utils/autostart.rs index 565f715051..df04b8100a 100644 --- a/src-tauri/src/utils/autostart.rs +++ b/src-tauri/src/utils/autostart.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Result}; use log::info; #[cfg(target_os = "windows")] -use std::{fs, path::Path, path::PathBuf}; +use std::{fs, os::windows::process::CommandExt, path::Path, path::PathBuf}; /// Windows 下的开机启动文件夹路径 #[cfg(target_os = "windows")] @@ -59,6 +59,8 @@ pub fn create_shortcut() -> Result<()> { let output = std::process::Command::new("powershell") .args(["-Command", &powershell_command]) + // 隐藏 PowerShell 窗口 + .creation_flags(0x08000000) // CREATE_NO_WINDOW .output() .map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?; From fee08f3826af5dce0d9e328fb7a3888e8d0f307d Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 22 Jun 2025 23:19:11 +0800 Subject: [PATCH 06/15] fix: correct address display error caused by async system proxy retrieval --- src/components/home/clash-info-card.tsx | 13 ++- .../setting/mods/sysproxy-viewer.tsx | 36 ++++++- src/providers/app-data-provider.tsx | 95 ++++++++++++++----- 3 files changed, 113 insertions(+), 31 deletions(-) diff --git a/src/components/home/clash-info-card.tsx b/src/components/home/clash-info-card.tsx index 029f174c82..dcdd22a8e5 100644 --- a/src/components/home/clash-info-card.tsx +++ b/src/components/home/clash-info-card.tsx @@ -17,7 +17,7 @@ const formatUptime = (uptimeMs: number) => { export const ClashInfoCard = () => { const { t } = useTranslation(); const { version: clashVersion } = useClash(); - const { clashConfig, sysproxy, rules, uptime } = useAppData(); + const { clashConfig, rules, uptime, systemProxyAddress } = useAppData(); // 使用useMemo缓存格式化后的uptime,避免频繁计算 const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]); @@ -42,7 +42,7 @@ export const ClashInfoCard = () => { {t("System Proxy Address")} - {sysproxy?.server || "-"} + {systemProxyAddress} @@ -74,7 +74,14 @@ export const ClashInfoCard = () => { ); - }, [clashConfig, clashVersion, t, formattedUptime, rules.length, sysproxy]); + }, [ + clashConfig, + clashVersion, + t, + formattedUptime, + rules.length, + systemProxyAddress, + ]); return ( ((props, ref) => { } }; + const { systemProxyAddress } = useAppData(); + + // 为当前状态计算系统代理地址 + const getSystemProxyAddress = useMemo(() => { + if (!clashConfig) return "-"; + + const isPacMode = value.pac ?? false; + + if (isPacMode) { + const host = value.proxy_host || "127.0.0.1"; + const port = verge?.verge_mixed_port || clashConfig["mixed-port"] || 7897; + return `${host}:${port}`; + } else { + return systemProxyAddress; + } + }, [ + value.pac, + value.proxy_host, + verge?.verge_mixed_port, + clashConfig, + systemProxyAddress, + ]); + const getCurrentPacUrl = useMemo(() => { + const host = value.proxy_host || "127.0.0.1"; + // 根据环境判断PAC端口 + const port = import.meta.env.DEV ? 11233 : 33331; + return `http://${host}:${port}/commands/pac`; + }, [value.proxy_host]); + useImperativeHandle(ref, () => ({ open: () => { setOpen(true); @@ -417,7 +447,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { {t("Server Addr")} - {sysproxy?.server ? sysproxy.server : t("Not available")} + {getSystemProxyAddress} @@ -425,7 +455,9 @@ export const SysproxyViewer = forwardRef((props, ref) => { {value.pac && ( {t("PAC URL")} - {autoproxy?.url || "-"} + + {getCurrentPacUrl || "-"} + )} diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx index 166b8057b4..0fee8a817b 100644 --- a/src/providers/app-data-provider.tsx +++ b/src/providers/app-data-provider.tsx @@ -1,5 +1,12 @@ -import { createContext, useContext, useMemo, useEffect } from "react"; -import useSWR from "swr"; +import React, { + createContext, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { useVerge } from "@/hooks/use-verge"; +import useSWR, { mutate } from "swr"; import useSWRSubscription from "swr/subscription"; import { getProxies, @@ -37,6 +44,8 @@ interface AppDataContextType { }; traffic: { up: number; down: number }; memory: { inuse: number }; + systemProxyAddress: string; + refreshProxy: () => Promise; refreshClashConfig: () => Promise; refreshRules: () => Promise; @@ -55,8 +64,9 @@ export const AppDataProvider = ({ }: { children: React.ReactNode; }) => { - const { clashInfo } = useClashInfo(); const pageVisible = useVisibility(); + const { clashInfo } = useClashInfo(); + const { verge } = useVerge(); // 基础数据 - 中频率更新 (5秒) const { data: proxiesData, mutate: refreshProxy } = useSWR( @@ -508,8 +518,39 @@ export const AppDataProvider = ({ }; // 聚合所有数据 - const value = useMemo( - () => ({ + const value = useMemo(() => { + // 计算系统代理地址 + const calculateSystemProxyAddress = () => { + if (!verge || !clashConfig) return "-"; + + const isPacMode = verge.proxy_auto_config ?? false; + + if (isPacMode) { + // PAC模式:显示我们期望设置的代理地址 + const proxyHost = verge.proxy_host || "127.0.0.1"; + const proxyPort = + verge.verge_mixed_port || clashConfig["mixed-port"] || 7897; + return `${proxyHost}:${proxyPort}`; + } else { + // HTTP代理模式:优先使用系统地址,但如果格式不正确则使用期望地址 + const systemServer = sysproxy?.server; + if ( + systemServer && + systemServer !== "-" && + !systemServer.startsWith(":") + ) { + return systemServer; + } else { + // 系统地址无效,返回期望的代理地址 + const proxyHost = verge.proxy_host || "127.0.0.1"; + const proxyPort = + verge.verge_mixed_port || clashConfig["mixed-port"] || 7897; + return `${proxyHost}:${proxyPort}`; + } + } + }; + + return { // 数据 proxies: proxiesData, clashConfig, @@ -534,6 +575,8 @@ export const AppDataProvider = ({ traffic: trafficData, memory: memoryData, + systemProxyAddress: calculateSystemProxyAddress(), + // 刷新方法 refreshProxy, refreshClashConfig, @@ -542,27 +585,27 @@ export const AppDataProvider = ({ refreshProxyProviders, refreshRuleProviders, refreshAll, - }), - [ - proxiesData, - clashConfig, - rulesData, - sysproxy, - runningMode, - uptimeData, - connectionsData, - trafficData, - memoryData, - proxyProviders, - ruleProviders, - refreshProxy, - refreshClashConfig, - refreshRules, - refreshSysproxy, - refreshProxyProviders, - refreshRuleProviders, - ], - ); + }; + }, [ + proxiesData, + clashConfig, + rulesData, + sysproxy, + runningMode, + uptimeData, + connectionsData, + trafficData, + memoryData, + proxyProviders, + ruleProviders, + verge, + refreshProxy, + refreshClashConfig, + refreshRules, + refreshSysproxy, + refreshProxyProviders, + refreshRuleProviders, + ]); return ( {children} From 628de70e89fb06a21ed187adfe9dba445803cbe6 Mon Sep 17 00:00:00 2001 From: wonfen Date: Mon, 23 Jun 2025 00:06:47 +0800 Subject: [PATCH 07/15] chore: remove unused imports --- package.json | 8 +- pnpm-lock.yaml | 145 +----------------- src/components/home/clash-mode-card.tsx | 2 +- .../home/enhanced-traffic-stats.tsx | 2 +- src/components/home/ip-info-card.tsx | 9 +- src/components/layout/layout-traffic.tsx | 2 +- .../profile/proxies-editor-viewer.tsx | 2 +- .../profile/rules-editor-viewer.tsx | 2 +- src/components/proxy/use-render-list.ts | 2 +- .../setting/mods/controller-viewer.tsx | 2 +- src/components/setting/setting-system.tsx | 3 +- .../shared/ProxyControlSwitches.tsx | 4 - src/hooks/use-log-data.ts | 6 - src/pages/_layout.tsx | 1 - src/pages/connections.tsx | 5 +- src/pages/home.tsx | 6 +- src/pages/profiles.tsx | 2 +- src/providers/app-data-provider.tsx | 10 +- src/services/api.ts | 1 - src/services/global-log-service.ts | 3 +- src/utils/uri-parser.ts | 2 +- 21 files changed, 25 insertions(+), 194 deletions(-) diff --git a/package.json b/package.json index 715a6fc213..3c720f8e00 100644 --- a/package.json +++ b/package.json @@ -45,24 +45,21 @@ "@tauri-apps/plugin-shell": "2.2.2", "@tauri-apps/plugin-updater": "2.8.1", "@tauri-apps/plugin-window-state": "^2.2.3", - "@types/d3-shape": "^3.1.7", "@types/json-schema": "^7.0.15", + "json-schema": "^0.4.0", "ahooks": "^3.8.5", "axios": "^1.10.0", "chart.js": "^4.5.0", "cli-color": "^2.0.4", - "d3-shape": "^3.2.0", "dayjs": "1.11.13", "foxact": "^0.2.49", "glob": "^11.0.3", "i18next": "^25.2.1", - "js-base64": "^3.7.7", "js-yaml": "^4.1.0", "lodash-es": "^4.17.21", "monaco-editor": "^0.52.2", "monaco-yaml": "^5.4.0", "nanoid": "^5.1.5", - "peggy": "^5.0.4", "react": "19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "19.1.0", @@ -82,7 +79,6 @@ "devDependencies": { "@actions/github": "^6.0.1", "@tauri-apps/cli": "2.5.0", - "@types/js-cookie": "^3.0.6", "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", "@types/react": "19.1.8", @@ -93,11 +89,9 @@ "commander": "^14.0.0", "cross-env": "^7.0.3", "https-proxy-agent": "^7.0.6", - "husky": "^9.1.7", "meta-json-schema": "^1.19.10", "node-fetch": "^3.3.2", "prettier": "^3.5.3", - "pretty-quick": "^4.2.2", "sass": "^1.89.2", "terser": "^5.43.1", "typescript": "^5.8.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 026505cbde..568e04c6d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,9 +68,6 @@ importers: '@tauri-apps/plugin-window-state': specifier: ^2.2.3 version: 2.2.3 - '@types/d3-shape': - specifier: ^3.1.7 - version: 3.1.7 '@types/json-schema': specifier: ^7.0.15 version: 7.0.15 @@ -86,9 +83,6 @@ importers: cli-color: specifier: ^2.0.4 version: 2.0.4 - d3-shape: - specifier: ^3.2.0 - version: 3.2.0 dayjs: specifier: 1.11.13 version: 1.11.13 @@ -101,12 +95,12 @@ importers: i18next: specifier: ^25.2.1 version: 25.2.1(typescript@5.8.3) - js-base64: - specifier: ^3.7.7 - version: 3.7.7 js-yaml: specifier: ^4.1.0 version: 4.1.0 + json-schema: + specifier: ^0.4.0 + version: 0.4.0 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -119,9 +113,6 @@ importers: nanoid: specifier: ^5.1.5 version: 5.1.5 - peggy: - specifier: ^5.0.4 - version: 5.0.4 react: specifier: 19.1.0 version: 19.1.0 @@ -174,9 +165,6 @@ importers: '@tauri-apps/cli': specifier: 2.5.0 version: 2.5.0 - '@types/js-cookie': - specifier: ^3.0.6 - version: 3.0.6 '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 @@ -207,9 +195,6 @@ importers: https-proxy-agent: specifier: ^7.0.6 version: 7.0.6 - husky: - specifier: ^9.1.7 - version: 9.1.7 meta-json-schema: specifier: ^1.19.10 version: 1.19.10 @@ -219,9 +204,6 @@ importers: prettier: specifier: ^3.5.3 version: 3.5.3 - pretty-quick: - specifier: ^4.2.2 - version: 4.2.2(prettier@3.5.3) sass: specifier: ^1.89.2 version: 1.89.2 @@ -1288,14 +1270,6 @@ packages: resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} engines: {node: '>= 10.0.0'} - '@peggyjs/from-mem@2.0.1': - resolution: {integrity: sha512-5dAPJsLrb3KQahPb8kUqg9nGS2dKlMC4vCB3dMWoZIRqmPrNbBt6P6jidczFBoz+2EbFXBxXi0o9BUpEPHoD+g==} - engines: {node: '>=20'} - - '@pkgr/core@0.2.7': - resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -1592,12 +1566,6 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - - '@types/d3-shape@3.1.7': - resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} - '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1610,9 +1578,6 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/js-cookie@3.0.6': - resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} @@ -1875,14 +1840,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - d@1.0.2: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} @@ -2132,11 +2089,6 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - husky@9.1.7: - resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} - engines: {node: '>=18'} - hasBin: true - i18next@25.2.1: resolution: {integrity: sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==} peerDependencies: @@ -2145,10 +2097,6 @@ packages: typescript: optional: true - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - immutable@5.1.2: resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==} @@ -2211,9 +2159,6 @@ packages: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -2238,6 +2183,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -2435,10 +2383,6 @@ packages: peerDependencies: monaco-editor: '>=0.36' - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2512,11 +2456,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - peggy@5.0.4: - resolution: {integrity: sha512-NMRm2w2irCFbiOaejvcDEyn+DMUaGd8s4RT1ztj9Kr/kR367pziIvmjqJ0OFqcAg+LqT5tPGsW96MNT5gUNdUw==} - engines: {node: '>=20'} - hasBin: true - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2537,13 +2476,6 @@ packages: engines: {node: '>=14'} hasBin: true - pretty-quick@4.2.2: - resolution: {integrity: sha512-uAh96tBW1SsD34VhhDmWuEmqbpfYc/B3j++5MC/6b3Cb8Ow7NJsvKFhg0eoGu2xXX+o9RkahkTK6sUdd8E7g5w==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - prettier: ^3.0.0 - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2717,11 +2649,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} @@ -2746,10 +2673,6 @@ packages: sockette@2.0.6: resolution: {integrity: sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q==} - source-map-generator@2.0.1: - resolution: {integrity: sha512-AtEu86XavXC2HD/bQVQoDbovnTRZE/0QMAAHn6RxALAoLOM3a47IG06TpsJK23BnCmjbTYNLwx2vUhgzRYgGMA==} - engines: {node: '>=20'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2824,9 +2747,6 @@ packages: resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} engines: {node: '>=0.12'} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -4193,12 +4113,6 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.1 optional: true - '@peggyjs/from-mem@2.0.1': - dependencies: - semver: 7.7.2 - - '@pkgr/core@0.2.7': {} - '@popperjs/core@2.11.8': {} '@rolldown/pluginutils@1.0.0-beta.11': {} @@ -4447,12 +4361,6 @@ snapshots: dependencies: '@babel/types': 7.27.6 - '@types/d3-path@3.1.1': {} - - '@types/d3-shape@3.1.7': - dependencies: - '@types/d3-path': 3.1.1 - '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -4467,8 +4375,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/js-cookie@3.0.6': {} - '@types/js-yaml@4.0.9': {} '@types/json-schema@7.0.15': {} @@ -4728,12 +4634,6 @@ snapshots: csstype@3.1.3: {} - d3-path@3.1.0: {} - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - d@1.0.2: dependencies: es5-ext: 0.10.64 @@ -5020,16 +4920,12 @@ snapshots: transitivePeerDependencies: - supports-color - husky@9.1.7: {} - i18next@25.2.1(typescript@5.8.3): dependencies: '@babel/runtime': 7.27.6 optionalDependencies: typescript: 5.8.3 - ignore@7.0.5: {} - immutable@5.1.2: {} import-fresh@3.3.1: @@ -5081,8 +4977,6 @@ snapshots: dependencies: '@isaacs/cliui': 8.0.2 - js-base64@3.7.7: {} - js-cookie@3.0.5: {} js-tokens@4.0.0: {} @@ -5097,6 +4991,8 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema@0.4.0: {} + json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -5429,8 +5325,6 @@ snapshots: vscode-uri: 3.1.0 yaml: 2.7.1 - mri@1.2.0: {} - ms@2.1.3: {} nanoid@3.3.11: {} @@ -5499,12 +5393,6 @@ snapshots: path-type@4.0.0: {} - peggy@5.0.4: - dependencies: - '@peggyjs/from-mem': 2.0.1 - commander: 14.0.0 - source-map-generator: 2.0.1 - picocolors@1.1.1: {} picomatch@2.3.1: @@ -5520,17 +5408,6 @@ snapshots: prettier@3.5.3: {} - pretty-quick@4.2.2(prettier@3.5.3): - dependencies: - '@pkgr/core': 0.2.7 - ignore: 7.0.5 - mri: 1.2.0 - picocolors: 1.1.1 - picomatch: 4.0.2 - prettier: 3.5.3 - tinyexec: 0.3.2 - tslib: 2.8.1 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -5726,8 +5603,6 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} - server-only@0.0.1: {} set-cookie-parser@2.7.1: {} @@ -5747,8 +5622,6 @@ snapshots: sockette@2.0.6: {} - source-map-generator@2.0.1: {} - source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -5830,8 +5703,6 @@ snapshots: es5-ext: 0.10.64 next-tick: 1.1.0 - tinyexec@0.3.2: {} - tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.2) diff --git a/src/components/home/clash-mode-card.tsx b/src/components/home/clash-mode-card.tsx index ceda0aecae..8e9572a265 100644 --- a/src/components/home/clash-mode-card.tsx +++ b/src/components/home/clash-mode-card.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Box, Typography, Paper, Stack, Fade } from "@mui/material"; +import { Box, Typography, Paper, Stack } from "@mui/material"; import { useLockFn } from "ahooks"; import { closeAllConnections } from "@/services/api"; import { patchClashMode } from "@/services/cmds"; diff --git a/src/components/home/enhanced-traffic-stats.tsx b/src/components/home/enhanced-traffic-stats.tsx index 29999f31c8..763c3fd416 100644 --- a/src/components/home/enhanced-traffic-stats.tsx +++ b/src/components/home/enhanced-traffic-stats.tsx @@ -26,7 +26,7 @@ import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { createAuthSockette } from "@/utils/websocket"; import parseTraffic from "@/utils/parse-traffic"; -import { getConnections, isDebugEnabled, gc } from "@/services/api"; +import { isDebugEnabled, gc } from "@/services/api"; import { ReactNode } from "react"; import { useAppData } from "@/providers/app-data-provider"; diff --git a/src/components/home/ip-info-card.tsx b/src/components/home/ip-info-card.tsx index f831ffe823..4c5231376c 100644 --- a/src/components/home/ip-info-card.tsx +++ b/src/components/home/ip-info-card.tsx @@ -1,12 +1,5 @@ import { useTranslation } from "react-i18next"; -import { - Box, - Typography, - Button, - Skeleton, - IconButton, - useTheme, -} from "@mui/material"; +import { Box, Typography, Button, Skeleton, IconButton } from "@mui/material"; import { LocationOnOutlined, RefreshOutlined, diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 8d89642200..54308d863a 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -11,7 +11,7 @@ import { TrafficGraph, type TrafficRef } from "./traffic-graph"; import { useVisibility } from "@/hooks/use-visibility"; import parseTraffic from "@/utils/parse-traffic"; import useSWRSubscription from "swr/subscription"; -import { createSockette, createAuthSockette } from "@/utils/websocket"; +import { createAuthSockette } from "@/utils/websocket"; import { useTranslation } from "react-i18next"; import { isDebugEnabled, gc } from "@/services/api"; diff --git a/src/components/profile/proxies-editor-viewer.tsx b/src/components/profile/proxies-editor-viewer.tsx index 34835fb7f2..624d83f9e8 100644 --- a/src/components/profile/proxies-editor-viewer.tsx +++ b/src/components/profile/proxies-editor-viewer.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useLockFn } from "ahooks"; import yaml from "js-yaml"; import { useTranslation } from "react-i18next"; diff --git a/src/components/profile/rules-editor-viewer.tsx b/src/components/profile/rules-editor-viewer.tsx index 854062d36d..85000813e0 100644 --- a/src/components/profile/rules-editor-viewer.tsx +++ b/src/components/profile/rules-editor-viewer.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useLockFn } from "ahooks"; import yaml from "js-yaml"; import { useTranslation } from "react-i18next"; diff --git a/src/components/proxy/use-render-list.ts b/src/components/proxy/use-render-list.ts index 4bb9aadd6e..90bcc457c4 100644 --- a/src/components/proxy/use-render-list.ts +++ b/src/components/proxy/use-render-list.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useCallback } from "react"; +import { useEffect, useMemo } from "react"; import { useVerge } from "@/hooks/use-verge"; import { filterSort } from "./use-filter-sort"; import { useWindowWidth } from "./use-window-width"; diff --git a/src/components/setting/mods/controller-viewer.tsx b/src/components/setting/mods/controller-viewer.tsx index 5734d2b78a..7c0b64811d 100644 --- a/src/components/setting/mods/controller-viewer.tsx +++ b/src/components/setting/mods/controller-viewer.tsx @@ -15,7 +15,7 @@ import { Tooltip, } from "@mui/material"; import { useLockFn } from "ahooks"; -import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; +import { forwardRef, useImperativeHandle, useState } from "react"; import { useTranslation } from "react-i18next"; export const ControllerViewer = forwardRef((props, ref) => { diff --git a/src/components/setting/setting-system.tsx b/src/components/setting/setting-system.tsx index f892bc8ebf..87079784eb 100644 --- a/src/components/setting/setting-system.tsx +++ b/src/components/setting/setting-system.tsx @@ -1,5 +1,5 @@ import useSWR, { mutate } from "swr"; -import { useRef, useEffect, useState } from "react"; +import { useRef } from "react"; import { useTranslation } from "react-i18next"; import { SettingsRounded, @@ -19,7 +19,6 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { getSystemProxy, getAutotemProxy, - installService, uninstallService, restartCore, stopCore, diff --git a/src/components/shared/ProxyControlSwitches.tsx b/src/components/shared/ProxyControlSwitches.tsx index 18a2b1375d..dcf4a651c2 100644 --- a/src/components/shared/ProxyControlSwitches.tsx +++ b/src/components/shared/ProxyControlSwitches.tsx @@ -24,11 +24,7 @@ import { getSystemProxy, getAutotemProxy, getRunningMode, - installService, - restartCore, - isServiceAvailable, } from "@/services/cmds"; -import { useLockFn } from "ahooks"; import { closeAllConnections } from "@/services/api"; import { showNotice } from "@/services/noticeService"; import { useServiceInstaller } from "@/hooks/useServiceInstaller"; diff --git a/src/hooks/use-log-data.ts b/src/hooks/use-log-data.ts index 8ceebb3cf0..cecf7168a7 100644 --- a/src/hooks/use-log-data.ts +++ b/src/hooks/use-log-data.ts @@ -1,10 +1,4 @@ -import { useEffect } from "react"; -import { useEnableLog } from "../services/states"; -import { createSockette, createAuthSockette } from "../utils/websocket"; -import { useClashInfo } from "./use-clash"; -import dayjs from "dayjs"; import { create } from "zustand"; -import { useVisibility } from "./use-visibility"; import { useGlobalLogData, clearGlobalLogs, diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index 2d763f5a38..50657fc2d8 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -21,7 +21,6 @@ import { useCustomTheme } from "@/components/layout/use-custom-theme"; import getSystem from "@/utils/get-system"; import "dayjs/locale/ru"; import "dayjs/locale/zh-cn"; -import { getPortableFlag } from "@/services/cmds"; import React from "react"; import { useListen } from "@/hooks/use-listen"; import { listen } from "@tauri-apps/api/event"; diff --git a/src/pages/connections.tsx b/src/pages/connections.tsx index b0da5d125d..4c8b6d4c11 100644 --- a/src/pages/connections.tsx +++ b/src/pages/connections.tsx @@ -19,10 +19,7 @@ import { ConnectionDetailRef, } from "@/components/connection/connection-detail"; import parseTraffic from "@/utils/parse-traffic"; -import { - BaseSearchBox, - type SearchState, -} from "@/components/base/base-search-box"; +import { BaseSearchBox } from "@/components/base/base-search-box"; import { BaseStyledSelect } from "@/components/base/base-styled-select"; import { useTheme } from "@mui/material/styles"; import { useVisibility } from "@/hooks/use-visibility"; diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 7e910f1251..1d9fbaa864 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -37,11 +37,7 @@ import { BasePage } from "@/components/base"; import { ClashInfoCard } from "@/components/home/clash-info-card"; import { SystemInfoCard } from "@/components/home/system-info-card"; import { useLockFn } from "ahooks"; -import { - entry_lightweight_mode, - openWebUrl, - patchVergeConfig, -} from "@/services/cmds"; +import { entry_lightweight_mode, openWebUrl } from "@/services/cmds"; import { TestCard } from "@/components/home/test-card"; import { IpInfoCard } from "@/components/home/ip-info-card"; diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 7d75e401bb..b9d74ac115 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -45,7 +45,7 @@ import { ProfileMore } from "@/components/profile/profile-more"; import { ProfileItem } from "@/components/profile/profile-item"; import { useProfiles } from "@/hooks/use-profiles"; import { ConfigViewer } from "@/components/setting/mods/config-viewer"; -import { add, throttle } from "lodash-es"; +import { throttle } from "lodash-es"; import { BaseStyledTextField } from "@/components/base/base-styled-text-field"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { readText } from "@tauri-apps/plugin-clipboard-manager"; diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx index 0fee8a817b..337c74de53 100644 --- a/src/providers/app-data-provider.tsx +++ b/src/providers/app-data-provider.tsx @@ -1,12 +1,6 @@ -import React, { - createContext, - useContext, - useEffect, - useMemo, - useState, -} from "react"; +import React, { createContext, useContext, useEffect, useMemo } from "react"; import { useVerge } from "@/hooks/use-verge"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; import useSWRSubscription from "swr/subscription"; import { getProxies, diff --git a/src/services/api.ts b/src/services/api.ts index cebaec84d4..f108ebb306 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,7 +1,6 @@ import axios, { AxiosInstance } from "axios"; import { getClashInfo } from "./cmds"; import { invoke } from "@tauri-apps/api/core"; -import { useLockFn } from "ahooks"; let instancePromise: Promise = null!; diff --git a/src/services/global-log-service.ts b/src/services/global-log-service.ts index b8d8731c77..8e94832521 100644 --- a/src/services/global-log-service.ts +++ b/src/services/global-log-service.ts @@ -1,8 +1,7 @@ // 全局日志服务,使应用在任何页面都能收集日志 import { create } from "zustand"; -import { createSockette, createAuthSockette } from "@/utils/websocket"; +import { createAuthSockette } from "@/utils/websocket"; import dayjs from "dayjs"; -import { useState, useEffect } from "react"; // 最大日志数量 const MAX_LOG_NUM = 1000; diff --git a/src/utils/uri-parser.ts b/src/utils/uri-parser.ts index eb8b630420..7aba40a5a5 100644 --- a/src/utils/uri-parser.ts +++ b/src/utils/uri-parser.ts @@ -730,7 +730,7 @@ function URI_Trojan(line: string): IProxyTrojanConfig { function URI_Hysteria2(line: string): IProxyHysteria2Config { line = line.split(/(hysteria2|hy2):\/\//)[2]; - // eslint-disable-next-line no-unused-vars + let [__, password, server, ___, port, ____, addons = "", name] = /^(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line) || []; let portNum = parseInt(`${port}`, 10); From d5a174c71b0030337230d3ddd63c8999aa7374b0 Mon Sep 17 00:00:00 2001 From: wonfen Date: Mon, 23 Jun 2025 12:59:24 +0800 Subject: [PATCH 08/15] perf: improve proxy status indicator and toggle responsiveness --- src/components/home/proxy-tun-card.tsx | 10 ++- src/components/setting/setting-system.tsx | 82 ++++--------------- .../shared/ProxyControlSwitches.tsx | 45 ++++------ src/hooks/use-system-proxy-state.ts | 72 ++++++++++++++++ src/providers/app-data-provider.tsx | 5 +- 5 files changed, 110 insertions(+), 104 deletions(-) create mode 100644 src/hooks/use-system-proxy-state.ts diff --git a/src/components/home/proxy-tun-card.tsx b/src/components/home/proxy-tun-card.tsx index 5803c32652..6dcba565af 100644 --- a/src/components/home/proxy-tun-card.tsx +++ b/src/components/home/proxy-tun-card.tsx @@ -19,6 +19,7 @@ import { } from "@mui/icons-material"; import { useVerge } from "@/hooks/use-verge"; import { useSystemState } from "@/hooks/use-system-state"; +import { useSystemProxyState } from "@/hooks/use-system-proxy-state"; import { showNotice } from "@/services/noticeService"; import { getRunningMode } from "@/services/cmds"; import { mutate } from "swr"; @@ -145,8 +146,9 @@ export const ProxyTunCard: FC = () => { const { verge } = useVerge(); const { isAdminMode } = useSystemState(); + const { indicator: systemProxyIndicator } = useSystemProxyState(); - const { enable_system_proxy, enable_tun_mode } = verge ?? {}; + const { enable_tun_mode } = verge ?? {}; const updateLocalStatus = async () => { try { @@ -180,7 +182,7 @@ export const ProxyTunCard: FC = () => { const tabDescription = useMemo(() => { if (activeTab === "system") { return { - text: enable_system_proxy + text: systemProxyIndicator ? t("System Proxy Enabled") : t("System Proxy Disabled"), tooltip: t("System Proxy Info"), @@ -195,7 +197,7 @@ export const ProxyTunCard: FC = () => { tooltip: t("TUN Mode Intercept Info"), }; } - }, [activeTab, enable_system_proxy, enable_tun_mode, isTunAvailable, t]); + }, [activeTab, systemProxyIndicator, enable_tun_mode, isTunAvailable, t]); return ( @@ -214,7 +216,7 @@ export const ProxyTunCard: FC = () => { onClick={() => handleTabChange("system")} icon={ComputerRounded} label={t("System Proxy")} - hasIndicator={enable_system_proxy} + hasIndicator={systemProxyIndicator} /> { const { verge, mutateVerge, patchVerge } = useVerge(); const { installServiceAndRestartCore } = useServiceInstaller(); - - const { data: sysproxy } = useSWR("getSystemProxy", getSystemProxy); - const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy); + const { + actualState: systemProxyActualState, + indicator: systemProxyIndicator, + toggleSystemProxy, + } = useSystemProxyState(); const { isAdminMode, isServiceMode, mutateRunningMode } = useSystemState(); @@ -51,26 +48,14 @@ const SettingSystem = ({ onError }: Props) => { const sysproxyRef = useRef(null); const tunRef = useRef(null); - const { - enable_tun_mode, - enable_auto_launch, - enable_silent_start, - enable_system_proxy, - proxy_auto_config, - enable_hover_jump_navigator, - } = verge ?? {}; + const { enable_tun_mode, enable_auto_launch, enable_silent_start } = + verge ?? {}; const onSwitchFormat = (_e: any, value: boolean) => value; const onChangeData = (patch: Partial) => { mutateVerge({ ...verge, ...patch }, false); }; - const updateProxyStatus = async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - await mutate("getSystemProxy"); - await mutate("getAutotemProxy"); - }; - // 抽象服务操作逻辑 const handleServiceOperation = useLockFn( async ({ @@ -194,13 +179,7 @@ const SettingSystem = ({ onError }: Props) => { icon={SettingsRounded} onClick={() => sysproxyRef.current?.open()} /> - {proxy_auto_config ? ( - autoproxy?.enable ? ( - - ) : ( - - ) - ) : sysproxy?.enable ? ( + {systemProxyIndicator ? ( ) : ( @@ -209,44 +188,13 @@ const SettingSystem = ({ onError }: Props) => { } > { - if (autoproxy?.enable === false && sysproxy?.enable === false) { - onChangeData({ enable_system_proxy: !enable_system_proxy }); - } else { - onChangeData({ enable_system_proxy: e }); - } - }} - onGuard={async (e) => { - if (autoproxy?.enable === false && sysproxy?.enable === false) { - await patchVerge({ enable_system_proxy: !enable_system_proxy }); - await updateProxyStatus(); - return; - } - if (!e && verge?.auto_close_connection) { - closeAllConnections(); - } - await patchVerge({ enable_system_proxy: e }); - await updateProxyStatus(); - }} + onGuard={(e) => toggleSystemProxy(e)} > - + diff --git a/src/components/shared/ProxyControlSwitches.tsx b/src/components/shared/ProxyControlSwitches.tsx index dcf4a651c2..f96d5cbeb5 100644 --- a/src/components/shared/ProxyControlSwitches.tsx +++ b/src/components/shared/ProxyControlSwitches.tsx @@ -1,6 +1,6 @@ import { useRef } from "react"; import { useTranslation } from "react-i18next"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; import { SettingsRounded, PlayCircleOutlineRounded, @@ -20,12 +20,8 @@ import { GuardState } from "@/components/setting/mods/guard-state"; import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer"; import { TunViewer } from "@/components/setting/mods/tun-viewer"; import { useVerge } from "@/hooks/use-verge"; -import { - getSystemProxy, - getAutotemProxy, - getRunningMode, -} from "@/services/cmds"; -import { closeAllConnections } from "@/services/api"; +import { useSystemProxyState } from "@/hooks/use-system-proxy-state"; +import { getRunningMode } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import { useServiceInstaller } from "@/hooks/useServiceInstaller"; @@ -44,12 +40,13 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => { const theme = useTheme(); const { installServiceAndRestartCore } = useServiceInstaller(); - const { data: sysproxy } = useSWR("getSystemProxy", getSystemProxy); - const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy); - const { data: runningMode, mutate: mutateRunningMode } = useSWR( - "getRunningMode", - getRunningMode, - ); + const { + actualState: systemProxyActualState, + indicator: systemProxyIndicator, + toggleSystemProxy, + } = useSystemProxyState(); + + const { data: runningMode } = useSWR("getRunningMode", getRunningMode); // 是否以sidecar模式运行 const isSidecarMode = runningMode === "Sidecar"; @@ -57,8 +54,7 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => { const sysproxyRef = useRef(null); const tunRef = useRef(null); - const { enable_tun_mode, enable_system_proxy, proxy_auto_config } = - verge ?? {}; + const { enable_tun_mode, enable_system_proxy } = verge ?? {}; // 确定当前显示哪个开关 const isSystemProxyMode = label === t("System Proxy") || !label; @@ -69,12 +65,6 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => { mutateVerge({ ...verge, ...patch }, false); }; - const updateProxyStatus = async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); - await mutate("getSystemProxy"); - await mutate("getAutotemProxy"); - }; - // 安装系统服务 const onInstallService = installServiceAndRestartCore; @@ -109,7 +99,7 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => { }} > - {enable_system_proxy ? ( + {systemProxyIndicator ? ( @@ -151,18 +141,11 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => { onChangeData({ enable_system_proxy: e })} - onGuard={async (e) => { - if (!e && verge?.auto_close_connection) { - closeAllConnections(); - } - await patchVerge({ enable_system_proxy: e }); - await updateProxyStatus(); - }} + onGuard={(e) => toggleSystemProxy(e)} > diff --git a/src/hooks/use-system-proxy-state.ts b/src/hooks/use-system-proxy-state.ts new file mode 100644 index 0000000000..2e4621ba18 --- /dev/null +++ b/src/hooks/use-system-proxy-state.ts @@ -0,0 +1,72 @@ +import useSWR, { mutate } from "swr"; +import { useVerge } from "@/hooks/use-verge"; +import { getAutotemProxy } from "@/services/cmds"; +import { useAppData } from "@/providers/app-data-provider"; +import { closeAllConnections } from "@/services/api"; + +// 系统代理状态检测统一逻辑 +export const useSystemProxyState = () => { + const { verge, mutateVerge, patchVerge } = useVerge(); + const { sysproxy } = useAppData(); + const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy, { + revalidateOnFocus: true, + revalidateOnReconnect: true, + }); + + const { enable_system_proxy, proxy_auto_config } = verge ?? {}; + + const getSystemProxyActualState = () => { + const userEnabled = enable_system_proxy ?? false; + + if (userEnabled) { + return true; + } + + return autoproxy?.enable === false && sysproxy?.enable === false + ? false + : userEnabled; + }; + + const getSystemProxyIndicator = () => { + if (proxy_auto_config) { + return autoproxy?.enable ?? false; + } else { + return sysproxy?.enable ?? false; + } + }; + + const updateProxyStatus = async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + await mutate("getSystemProxy"); + await mutate("getAutotemProxy"); + }; + + const toggleSystemProxy = (enabled: boolean) => { + mutateVerge({ ...verge, enable_system_proxy: enabled }, false); + + setTimeout(async () => { + try { + if (!enabled && verge?.auto_close_connection) { + closeAllConnections(); + } + await patchVerge({ enable_system_proxy: enabled }); + + updateProxyStatus(); + } catch (error) { + mutateVerge({ ...verge, enable_system_proxy: !enabled }, false); + } + }, 0); + + return Promise.resolve(); + }; + + return { + actualState: getSystemProxyActualState(), + indicator: getSystemProxyIndicator(), + configState: enable_system_proxy ?? false, + sysproxy, + autoproxy, + proxy_auto_config, + toggleSystemProxy, + }; +}; diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx index 337c74de53..9e7ab3afd8 100644 --- a/src/providers/app-data-provider.tsx +++ b/src/providers/app-data-provider.tsx @@ -68,7 +68,7 @@ export const AppDataProvider = ({ getProxies, { refreshInterval: 5000, - revalidateOnFocus: false, + revalidateOnFocus: true, suspense: false, errorRetryCount: 3, }, @@ -243,7 +243,8 @@ export const AppDataProvider = ({ "getSystemProxy", getSystemProxy, { - revalidateOnFocus: false, + revalidateOnFocus: true, + revalidateOnReconnect: true, suspense: false, errorRetryCount: 3, }, From 6d519dac1e329cec77939de13c1fba1792146f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B8=8C=E4=BA=9A=E7=9A=84=E8=A5=BF=E7=BA=A2=E6=9F=BF?= Date: Mon, 23 Jun 2025 23:13:31 +0800 Subject: [PATCH 09/15] fix: auto light-weight mode doesn't take effect in silent-start mode (#3875) * fix: auto light-weight mode does not take effect when silent-start mode is enabled * refactor: streamline window state retrieval and hiding logic * fix: add checks for remote name and existence before format check in pre-push hook * fix: simplify remote checks in pre-push hook to enhance clarity and maintainability --------- Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com> --- .husky/pre-push | 8 ++--- UPDATELOG.md | 2 ++ src-tauri/src/module/lightweight.rs | 21 +++++++----- src-tauri/src/utils/resolve.rs | 1 + src-tauri/src/utils/window_manager.rs | 49 ++++++++++++++++----------- 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/.husky/pre-push b/.husky/pre-push index 0c95cbfdac..e7b0a02e9c 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -8,11 +8,9 @@ if git diff --cached --name-only | grep -q '^src-tauri/'; then fi fi -remote_name="$1" -remote_url=$(git remote get-url "$remote_name") - -if [[ "$remote_url" =~ github\.com[:/]+clash-verge-rev/clash-verge-rev(\.git)?$ ]]; then - echo "[pre-push] Detected push to clash-verge-rev/clash-verge-rev ($remote_url)" +# 检查所有 remote url 是否有目标仓库 +if git remote -v | grep -Eq 'github\\.com[:/]+clash-verge-rev/clash-verge-rev(\\.git)?'; then + echo "[pre-push] Detected push to clash-verge-rev/clash-verge-rev" echo "[pre-push] Running pnpm format:check..." pnpm format:check diff --git a/UPDATELOG.md b/UPDATELOG.md index 5472ddc2d9..76430d0636 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -5,6 +5,8 @@ - 修复系统代理端口不同步问题 - 修复自定义 `css` 背景图无法生效问题 - 修复在轻量模式下快速点击托盘图标带来的竞争态卡死问题 +- 修复同时开启静默启动与自动进入轻量模式后,自动进入轻量模式失效的问题 +- 修复静默启动时托盘工具栏轻量模式开启与关闭状态的同步 ### ✨ 新增功能 diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index f93cc170c2..0b8553e530 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -36,20 +36,20 @@ where pub fn run_once_auto_lightweight() { LightWeightState::default().run_once_time(|| { - let is_silent_start = Config::verge().data().enable_silent_start.unwrap_or(true); + let is_silent_start = Config::verge().data().enable_silent_start.unwrap_or(false); let enable_auto = Config::verge() .data() .enable_auto_light_weight_mode - .unwrap_or(true); + .unwrap_or(false); if enable_auto && is_silent_start { logging!( info, Type::Lightweight, true, - "正常创建窗口和添加定时器监听器" + "在静默启动的情况下,创建窗口再添加自动进入轻量模式窗口监听器" ); set_lightweight_mode(false); - disable_auto_light_weight_mode(); + enable_auto_light_weight_mode(); // 触发托盘更新 if let Err(e) = Tray::global().update_part() { @@ -65,8 +65,13 @@ pub fn auto_lightweight_mode_init() { let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false); let enable_auto = { Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false); - if enable_auto && is_silent_start { - logging!(info, Type::Lightweight, true, "自动轻量模式静默启动"); + if enable_auto && !is_silent_start { + logging!( + info, + Type::Lightweight, + true, + "非静默启动直接挂载自动进入轻量模式监听器!" + ); set_lightweight_mode(true); enable_auto_light_weight_mode(); @@ -84,7 +89,7 @@ pub fn is_in_lightweight_mode() -> bool { } // 设置轻量模式状态 -fn set_lightweight_mode(value: bool) { +pub fn set_lightweight_mode(value: bool) { with_lightweight_status(|state| { state.set_lightweight_mode(value); }); @@ -126,7 +131,6 @@ pub fn entry_lightweight_mode() { } #[cfg(target_os = "macos")] AppHandleManager::global().set_activation_policy_accessory(); - logging!(info, Type::Lightweight, true, "轻量模式已开启"); } set_lightweight_mode(true); let _ = cancel_light_weight_timer(); @@ -163,7 +167,6 @@ pub fn exit_lightweight_mode() { } set_lightweight_mode(false); - logging!(info, Type::Lightweight, true, "正在退出轻量模式"); // macOS激活策略 #[cfg(target_os = "macos")] diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index dada58ec65..06701846f3 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -295,6 +295,7 @@ pub fn create_window(is_show: bool) -> bool { if !is_show { logging!(info, Type::Window, true, "静默模式启动时不创建窗口"); + lightweight::set_lightweight_mode(true); handle::Handle::notify_startup_completed(); return false; } diff --git a/src-tauri/src/utils/window_manager.rs b/src-tauri/src/utils/window_manager.rs index c06cb5190f..f627e213d4 100644 --- a/src-tauri/src/utils/window_manager.rs +++ b/src-tauri/src/utils/window_manager.rs @@ -86,20 +86,27 @@ pub struct WindowManager; impl WindowManager { pub fn get_main_window_state() -> WindowState { - if let Some(window) = Self::get_main_window() { - if window.is_minimized().unwrap_or(false) { - WindowState::Minimized - } else if window.is_visible().unwrap_or(false) { - if window.is_focused().unwrap_or(false) { + match Self::get_main_window() { + Some(window) => { + let is_minimized = window.is_minimized().unwrap_or(false); + let is_visible = window.is_visible().unwrap_or(false); + let is_focused = window.is_focused().unwrap_or(false); + + if is_minimized { + return WindowState::Minimized; + } + + if !is_visible { + return WindowState::Hidden; + } + + if is_focused { WindowState::VisibleFocused } else { WindowState::VisibleUnfocused } - } else { - WindowState::Hidden } - } else { - WindowState::NotExist + None => WindowState::NotExist, } } @@ -316,17 +323,21 @@ impl WindowManager { pub fn hide_main_window() -> WindowOperationResult { logging!(info, Type::Window, true, "开始隐藏主窗口"); - if let Some(window) = Self::get_main_window() { - if window.hide().is_ok() { - logging!(info, Type::Window, true, "窗口已隐藏"); - WindowOperationResult::Hidden - } else { - logging!(warn, Type::Window, true, "隐藏窗口失败"); - WindowOperationResult::Failed + match Self::get_main_window() { + Some(window) => match window.hide() { + Ok(_) => { + logging!(info, Type::Window, true, "窗口已隐藏"); + WindowOperationResult::Hidden + } + Err(e) => { + logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e); + WindowOperationResult::Failed + } + }, + None => { + logging!(info, Type::Window, true, "窗口不存在,无需隐藏"); + WindowOperationResult::NoAction } - } else { - logging!(info, Type::Window, true, "窗口不存在,无需隐藏"); - WindowOperationResult::NoAction } } From 0a8d6e5147a05bdb1dd73391dfee8a67db22a29e Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:30:33 +0800 Subject: [PATCH 10/15] fix: add validation for profile URL format during import --- UPDATELOG.md | 1 + src/locales/en.json | 3 ++- src/locales/ru.json | 3 ++- src/locales/zh.json | 3 ++- src/pages/profiles.tsx | 8 +++++--- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/UPDATELOG.md b/UPDATELOG.md index 76430d0636..fab7ad3620 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -7,6 +7,7 @@ - 修复在轻量模式下快速点击托盘图标带来的竞争态卡死问题 - 修复同时开启静默启动与自动进入轻量模式后,自动进入轻量模式失效的问题 - 修复静默启动时托盘工具栏轻量模式开启与关闭状态的同步 +- 修复导入订阅时非 http 协议链接被错误尝试导入 ### ✨ 新增功能 diff --git a/src/locales/en.json b/src/locales/en.json index eb3f8c6e9d..740911dd53 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -623,5 +623,6 @@ "Originals Only": "Originals Only", "No (IP Banned By Disney+)": "No (IP Banned By Disney+)", "Unsupported Country/Region": "Unsupported Country/Region", - "Failed (Network Connection)": "Failed (Network Connection)" + "Failed (Network Connection)": "Failed (Network Connection)", + "Invalid Profile URL": "Invalid profile URL. Please enter a URL starting with http:// or https://" } diff --git a/src/locales/ru.json b/src/locales/ru.json index f4aa2350ca..0bdeab4871 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -581,5 +581,6 @@ "Originals Only": "Только Originals", "No (IP Banned By Disney+)": "Нет (IP забанен Disney+)", "Unsupported Country/Region": "Страна/регион не поддерживается", - "Failed (Network Connection)": "Ошибка подключения" + "Failed (Network Connection)": "Ошибка подключения", + "Invalid Profile URL": "Недопустимая ссылка на профиль, введите адрес, начинающийся с http:// или https://" } diff --git a/src/locales/zh.json b/src/locales/zh.json index 4689bfe634..aa13fc3b68 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -623,5 +623,6 @@ "Originals Only": "仅限原创", "No (IP Banned By Disney+)": "不支持(IP被Disney+禁止)", "Unsupported Country/Region": "不支持的国家/地区", - "Failed (Network Connection)": "测试失败(网络连接问题)" + "Failed (Network Connection)": "测试失败(网络连接问题)", + "Invalid Profile URL": "无效的订阅链接,请输入以 http:// 或 https:// 开头的地址" } diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index b9d74ac115..71bfd73b27 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -227,8 +227,12 @@ const ProfilePage = () => { const onImport = async () => { if (!url) return; + // 校验url是否为http/https + if (!/^https?:\/\//i.test(url)) { + showNotice("error", t("Invalid Profile URL")); + return; + } setLoading(true); - try { // 尝试正常导入 await importProfile(url); @@ -240,14 +244,12 @@ const ProfilePage = () => { // 首次导入失败,尝试使用自身代理 const errmsg = err.message || err.toString(); showNotice("info", t("Import failed, retrying with Clash proxy...")); - try { // 使用自身代理尝试导入 await importProfile(url, { with_proxy: false, self_proxy: true, }); - // 回退导入成功 showNotice("success", t("Profile Imported with Clash proxy")); setUrl(""); From f22199b7d98bddfba69c934720082926d8645b4d Mon Sep 17 00:00:00 2001 From: TRH <43237288+trhao99@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:49:06 +0800 Subject: [PATCH 11/15] =?UTF-8?q?chore(logging):=20=E5=8E=BB=E9=99=A4?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E6=97=A5=E5=BF=97=20(#3882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(logging): 去除无用日志 * chore: 删除相关计时代码 --- src-tauri/src/utils/resolve.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 06701846f3..b90857bc89 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -52,14 +52,12 @@ pub enum UiReadyStage { #[derive(Debug)] struct UiReadyState { stage: RwLock, - last_update: RwLock, } impl Default for UiReadyState { fn default() -> Self { Self { stage: RwLock::new(UiReadyStage::NotStarted), - last_update: RwLock::new(Instant::now()), } } } @@ -83,20 +81,8 @@ fn get_ui_ready_state() -> &'static Arc { pub fn update_ui_ready_stage(stage: UiReadyStage) { let state = get_ui_ready_state(); let mut stage_lock = state.stage.write(); - let mut time_lock = state.last_update.write(); *stage_lock = stage; - *time_lock = Instant::now(); - - logging!( - info, - Type::Window, - true, - "UI准备阶段更新: {:?}, 耗时: {:?}ms", - stage, - time_lock.elapsed().as_millis() - ); - // 如果是最终阶段,标记UI完全就绪 if stage == UiReadyStage::Ready { mark_ui_ready(); @@ -119,9 +105,7 @@ pub fn reset_ui_ready() { { let state = get_ui_ready_state(); let mut stage = state.stage.write(); - let mut time = state.last_update.write(); *stage = UiReadyStage::NotStarted; - *time = Instant::now(); } logging!(info, Type::Window, true, "UI就绪状态已重置"); } From 7aee1c6d6e9ce919227cee50045fde367d5114ea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:09:36 +0800 Subject: [PATCH 12/15] chore(deps): update cargo dependencies (#3872) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src-tauri/Cargo.lock | 193 ++++++++++++++++++++----------------------- src-tauri/Cargo.toml | 18 ++-- 2 files changed, 97 insertions(+), 114 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 659f5dabc6..d539c0cd46 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -514,7 +514,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", - "itoa 1.0.15", + "itoa", "matchit", "memchr", "mime", @@ -801,9 +801,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -812,9 +812,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1411,15 +1411,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", + "itoa", "matches", - "phf 0.8.0", + "phf 0.10.1", "proc-macro2", "quote", "smallvec", @@ -2812,16 +2812,14 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", + "match_token", ] [[package]] @@ -2832,7 +2830,7 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -2843,7 +2841,7 @@ checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -2925,7 +2923,7 @@ dependencies = [ "http-body 0.4.6", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "pin-project-lite", "socket2 0.5.10", "tokio", @@ -2947,7 +2945,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "httparse", - "itoa 1.0.15", + "itoa", "pin-project-lite", "smallvec", "tokio", @@ -3422,12 +3420,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.15" @@ -3539,14 +3531,13 @@ dependencies = [ [[package]] name = "kuchikiki" -version = "0.8.2" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.3", - "matches", + "indexmap 2.8.0", "selectors", ] @@ -3794,18 +3785,29 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "matchers" version = "0.1.0" @@ -4910,9 +4912,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", "phf_shared 0.8.0", - "proc-macro-hack", ] [[package]] @@ -4921,7 +4921,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros 0.10.0", "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] @@ -4944,16 +4946,6 @@ dependencies = [ "phf_shared 0.8.0", ] -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - [[package]] name = "phf_codegen" version = "0.11.3" @@ -4996,12 +4988,12 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2", "quote", @@ -6105,22 +6097,20 @@ dependencies = [ [[package]] name = "selectors" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", "log", - "matches", "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -6235,7 +6225,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "itoa 1.0.15", + "itoa", "memchr", "ryu", "serde", @@ -6268,7 +6258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.15", + "itoa", "ryu", "serde", ] @@ -6310,7 +6300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap 2.8.0", - "itoa 1.0.15", + "itoa", "ryu", "serde", "unsafe-libyaml", @@ -6340,9 +6330,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ "nodrop", "stable_deref_trait", @@ -6771,9 +6761,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" +checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" dependencies = [ "bitflags 2.9.0", "core-foundation 0.10.0", @@ -6844,16 +6834,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" +checksum = "2f7a0f4019c80391d143ee26cd7cd1ed271ac241d3087d333f99f3269ba90812" dependencies = [ "anyhow", "bytes", "dirs 6.0.0", "dunce", "embed_plist", - "futures-util", "getrandom 0.2.15", "glob", "gtk", @@ -6898,9 +6887,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" +checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" dependencies = [ "anyhow", "cargo_toml", @@ -6920,9 +6909,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" +checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" dependencies = [ "base64 0.22.1", "brotli", @@ -6947,9 +6936,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" +checksum = "1f59e1d1fa9651212dcb890a0c66226d819b716490b0cf43c078514da3591705" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6961,9 +6950,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9972871fcbddf16618f70412d965d4d845cd4b76d03fff168709961ef71e5cdf" +checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" dependencies = [ "anyhow", "glob", @@ -6978,9 +6967,9 @@ dependencies = [ [[package]] name = "tauri-plugin-autostart" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9641831518c56775a364a8750e0eed8852adee87e0f11006d043b9ebba0bf5" +checksum = "062cdcd483d5e3148c9a64dabf8c574e239e2aa1193cf208d95cf89a676f87a5" dependencies = [ "auto-launch", "serde", @@ -7007,9 +6996,9 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4976ac728ebc0487515aa956cfdf200abcc52b784e441493fc544bc6ce369c8" +checksum = "ab261eb006db10ab478e3fbb5a4e2692df3f7eb3e28300ee2b64428979167ed0" dependencies = [ "dunce", "rust-ini", @@ -7054,9 +7043,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33318fe222fc2a612961de8b0419e2982767f213f54a4d3a21b0d7b85c41df8" +checksum = "1aefb14219b492afb30b12647b5b1247cadd2c0603467310c36e0f7ae1698c28" dependencies = [ "log", "raw-window-handle", @@ -7072,9 +7061,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da" +checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f" dependencies = [ "anyhow", "dunce", @@ -7094,9 +7083,9 @@ dependencies = [ [[package]] name = "tauri-plugin-global-shortcut" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31919f3c07bcb585afef217c0c33cde80da9ebccf5b8e2c90e0e0a535b14ab47" +checksum = "6df9f0f7bf2fe768b85fee4951c2505a35b72c44df1f6403e74e110bc13c5f58" dependencies = [ "global-hotkey", "log", @@ -7119,9 +7108,9 @@ dependencies = [ [[package]] name = "tauri-plugin-shell" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34e525a448b80ad5d906fcbd93838ac3ba37985b29ac699a045b5da9b0a1a22" +checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25" dependencies = [ "encoding_rs", "log", @@ -7172,9 +7161,9 @@ dependencies = [ [[package]] name = "tauri-plugin-window-state" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136e5ce5e61edc8eeeaca70080811bbdcdd890cac9c4070cb4db9cc3de1da449" +checksum = "5a3d22b21b9cec73601b512a868f7c74f93c044d44fd6ca1c84e9d6afb6b1559" dependencies = [ "bitflags 2.9.0", "log", @@ -7187,9 +7176,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" +checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" dependencies = [ "cookie", "dpi", @@ -7209,9 +7198,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" +checksum = "fe52ed0ef40fd7ad51a620ecb3018e32eba3040bb95025216a962a37f6f050c5" dependencies = [ "gtk", "http 1.3.1", @@ -7237,9 +7226,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" +checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" dependencies = [ "anyhow", "brotli", @@ -7361,12 +7350,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thin-vec" version = "0.2.14" @@ -7451,7 +7434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa 1.0.15", + "itoa", "js-sys", "libc", "num-conv", @@ -8543,9 +8526,9 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", @@ -8568,9 +8551,9 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ "thiserror 2.0.12", "windows 0.61.1", @@ -9222,9 +9205,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.51.2" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" +checksum = "b08db04817a654a7e3339647d9cf8b497ed9ddcd4ec7cfda5a3a220c10a3bba3" dependencies = [ "base64 0.22.1", "block2 0.6.0", @@ -9527,9 +9510,9 @@ dependencies = [ [[package]] name = "zip" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7dcdb4229c0e79c2531a24de7726a0e980417a74fb4d030a35f535665439a0" +checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899" dependencies = [ "aes", "arbitrary", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cd95b11b81..8988251a8f 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,7 +13,7 @@ build = "build.rs" identifier = "io.github.clash-verge-rev.clash-verge-rev" [build-dependencies] -tauri-build = { version = "2.2.0", features = [] } +tauri-build = { version = "2.3.0", features = [] } [dependencies] warp = "0.3.7" @@ -47,7 +47,7 @@ regex = "1.11.1" sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" } image = "0.25.6" imageproc = "0.25.0" -tauri = { version = "2.5.1", features = [ +tauri = { version = "2.6.0", features = [ "protocol-asset", "devtools", "tray-icon", @@ -55,15 +55,15 @@ tauri = { version = "2.5.1", features = [ "image-png", ] } network-interface = { version = "2.0.1", features = ["serde"] } -tauri-plugin-shell = "2.2.2" -tauri-plugin-dialog = "2.2.2" +tauri-plugin-shell = "2.3.0" +tauri-plugin-dialog = "2.3.0" tauri-plugin-fs = "2.3.0" tauri-plugin-process = "2.2.2" tauri-plugin-clipboard-manager = "2.2.3" -tauri-plugin-deep-link = "2.3.0" +tauri-plugin-deep-link = "2.4.0" tauri-plugin-devtools = "2.0.0" -tauri-plugin-window-state = "2.2.3" -zip = "4.1.0" +tauri-plugin-window-state = "2.3.0" +zip = "4.2.0" reqwest_dav = "0.2.1" aes-gcm = { version = "0.10.3", features = ["std"] } base64 = "0.22.1" @@ -104,8 +104,8 @@ winapi = { version = "0.3.9", features = [ users = "0.11.0" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] -tauri-plugin-autostart = "2.4.0" -tauri-plugin-global-shortcut = "2.2.1" +tauri-plugin-autostart = "2.5.0" +tauri-plugin-global-shortcut = "2.3.0" tauri-plugin-updater = "2.8.1" [features] From cb98b170524135f256586c690a3918ab44e30305 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:09:50 +0800 Subject: [PATCH 13/15] chore(deps): update npm dependencies to v7 (#3886) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- pnpm-lock.yaml | 118 ++++++++++++++++++++++++------------------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 3c720f8e00..29a6940f4a 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@types/lodash-es": "^4.17.12", "@types/react": "19.1.8", "@types/react-dom": "19.1.6", - "@vitejs/plugin-legacy": "^6.1.1", + "@vitejs/plugin-legacy": "^7.0.0", "@vitejs/plugin-react": "4.5.2", "adm-zip": "^0.5.16", "commander": "^14.0.0", @@ -95,7 +95,7 @@ "sass": "^1.89.2", "terser": "^5.43.1", "typescript": "^5.8.3", - "vite": "^6.3.5", + "vite": "^7.0.0", "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-svgr": "^4.3.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 568e04c6d8..d01d09be75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,11 +178,11 @@ importers: specifier: 19.1.6 version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-legacy': - specifier: ^6.1.1 - version: 6.1.1(terser@5.43.1)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) + specifier: ^7.0.0 + version: 7.0.0(terser@5.43.1)(vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) '@vitejs/plugin-react': specifier: 4.5.2 - version: 4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) + version: 4.5.2(vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -214,14 +214,14 @@ importers: specifier: ^5.8.3 version: 5.8.3 vite: - specifier: ^6.3.5 - version: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + specifier: ^7.0.0 + version: 7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) vite-plugin-monaco-editor: specifier: ^1.1.0 version: 1.1.0(monaco-editor@0.52.2) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) + version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)) packages: @@ -1624,12 +1624,12 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-legacy@6.1.1': - resolution: {integrity: sha512-BvusL+mYZ0q5qS5Rq3D70QxZBmhyiHRaXLtYJHH5AEsAmdSqJR4xe5KwMi1H3w8/9lVJwhkLYqFQ9vmWYWy6kA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + '@vitejs/plugin-legacy@7.0.0': + resolution: {integrity: sha512-qevhyYFUeZXBd/bAZGwpBgyn4GGAYije9YPV8Jg07newPCZtFEIlFlzsQowPbm87iKekOIL/90wKn+hvGkjzkg==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: terser: ^5.16.0 - vite: ^6.0.0 + vite: ^7.0.0 '@vitejs/plugin-react@4.5.2': resolution: {integrity: sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==} @@ -1717,8 +1717,8 @@ packages: peerDependencies: browserslist: '*' - browserslist@4.24.5: - resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==} + browserslist@4.25.0: + resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1812,8 +1812,8 @@ packages: core-js-compat@3.42.0: resolution: {integrity: sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==} - core-js@3.42.0: - resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==} + core-js@3.43.0: + resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==} cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} @@ -1895,8 +1895,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.152: - resolution: {integrity: sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==} + electron-to-chromium@1.5.173: + resolution: {integrity: sha512-2bFhXP2zqSfQHugjqJIDFVwa+qIxyNApenmXTp9EjaKtdPrES5Qcn9/aSFy/NaP2E+fWG/zxKu/LBvY36p5VNQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1977,8 +1977,8 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2467,8 +2467,8 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} prettier@3.5.3: @@ -2747,8 +2747,8 @@ packages: resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} engines: {node: '>=0.12'} - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} to-regex-range@5.0.1: @@ -2847,19 +2847,19 @@ packages: peerDependencies: vite: '>=2.6.0' - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.0.0: + resolution: {integrity: sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -3030,7 +3030,7 @@ snapshots: dependencies: '@babel/compat-data': 7.27.2 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.24.5 + browserslist: 4.25.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -4413,22 +4413,22 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-legacy@6.1.1(terser@5.43.1)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': + '@vitejs/plugin-legacy@7.0.0(terser@5.43.1)(vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': dependencies: '@babel/core': 7.27.4 '@babel/preset-env': 7.27.2(@babel/core@7.27.4) - browserslist: 4.24.5 - browserslist-to-esbuild: 2.1.1(browserslist@4.24.5) - core-js: 3.42.0 + browserslist: 4.25.0 + browserslist-to-esbuild: 2.1.1(browserslist@4.25.0) + core-js: 3.43.0 magic-string: 0.30.17 regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.43.1 - vite: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + vite: 7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': + '@vitejs/plugin-react@4.5.2(vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) @@ -4436,7 +4436,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.11 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + vite: 7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) transitivePeerDependencies: - supports-color @@ -4520,17 +4520,17 @@ snapshots: fill-range: 7.1.1 optional: true - browserslist-to-esbuild@2.1.1(browserslist@4.24.5): + browserslist-to-esbuild@2.1.1(browserslist@4.25.0): dependencies: - browserslist: 4.24.5 + browserslist: 4.25.0 meow: 13.2.0 - browserslist@4.24.5: + browserslist@4.25.0: dependencies: caniuse-lite: 1.0.30001718 - electron-to-chromium: 1.5.152 + electron-to-chromium: 1.5.173 node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.24.5) + update-browserslist-db: 1.1.3(browserslist@4.25.0) buffer-from@1.1.2: {} @@ -4601,9 +4601,9 @@ snapshots: core-js-compat@3.42.0: dependencies: - browserslist: 4.24.5 + browserslist: 4.25.0 - core-js@3.42.0: {} + core-js@3.43.0: {} cosmiconfig@7.1.0: dependencies: @@ -4682,7 +4682,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.152: {} + electron-to-chromium@1.5.173: {} emoji-regex@8.0.0: {} @@ -4790,7 +4790,7 @@ snapshots: extend@3.0.2: {} - fdir@6.4.4(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -5400,7 +5400,7 @@ snapshots: picomatch@4.0.2: {} - postcss@8.5.3: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -5703,9 +5703,9 @@ snapshots: es5-ext: 0.10.64 next-tick: 1.1.0 - tinyglobby@0.2.13: + tinyglobby@0.2.14: dependencies: - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 to-regex-range@5.0.1: @@ -5777,9 +5777,9 @@ snapshots: universal-user-agent@6.0.1: {} - update-browserslist-db@1.1.3(browserslist@4.24.5): + update-browserslist-db@1.1.3(browserslist@4.25.0): dependencies: - browserslist: 4.24.5 + browserslist: 4.25.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -5801,25 +5801,25 @@ snapshots: dependencies: monaco-editor: 0.52.2 - vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)): + vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)): dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.40.2) '@svgr/core': 8.1.0(typescript@5.8.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) - vite: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) + vite: 7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1): + vite@7.0.0(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1): dependencies: esbuild: 0.25.4 - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 - postcss: 8.5.3 + postcss: 8.5.6 rollup: 4.40.2 - tinyglobby: 0.2.13 + tinyglobby: 0.2.14 optionalDependencies: fsevents: 2.3.3 sass: 1.89.2 From 5db1f7cda788929c77915b3d0bbc8ab49ccaa25b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:29:13 +0800 Subject: [PATCH 14/15] chore(deps): update cargo dependencies (#3896) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src-tauri/Cargo.lock | 12 ++++++------ src-tauri/Cargo.toml | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d539c0cd46..70c8dee1d4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6981,9 +6981,9 @@ dependencies = [ [[package]] name = "tauri-plugin-clipboard-manager" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11fa4f17a6d380490597f7632aca40b65d379cb374cb92bd9d80f333309b7fd7" +checksum = "adddd9e9275b20e77af3061d100a25a884cced3c4c9ef680bd94dd0f7e26c1ca" dependencies = [ "arboard", "log", @@ -7098,9 +7098,9 @@ dependencies = [ [[package]] name = "tauri-plugin-process" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d870adae9408be585abd56eade2b5def2660339512b7c8de5ddf21238b67a34" +checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab" dependencies = [ "tauri", "tauri-plugin", @@ -7129,9 +7129,9 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b068673e9037376ca9906f99b00ae5f9e6eb62f456f900b4435c38d57cfa73e4" +checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b" dependencies = [ "base64 0.22.1", "dirs 6.0.0", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8988251a8f..1e5b6d454f 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -57,9 +57,9 @@ tauri = { version = "2.6.0", features = [ network-interface = { version = "2.0.1", features = ["serde"] } tauri-plugin-shell = "2.3.0" tauri-plugin-dialog = "2.3.0" -tauri-plugin-fs = "2.3.0" -tauri-plugin-process = "2.2.2" -tauri-plugin-clipboard-manager = "2.2.3" +tauri-plugin-fs = "2.4.0" +tauri-plugin-process = "2.3.0" +tauri-plugin-clipboard-manager = "2.3.0" tauri-plugin-deep-link = "2.4.0" tauri-plugin-devtools = "2.0.0" tauri-plugin-window-state = "2.3.0" @@ -106,7 +106,7 @@ users = "0.11.0" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-autostart = "2.5.0" tauri-plugin-global-shortcut = "2.3.0" -tauri-plugin-updater = "2.8.1" +tauri-plugin-updater = "2.9.0" [features] default = ["custom-protocol"] From a7875718f7e3fb671c7d5bd0ca7b8bc0a54881c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:31:28 +0800 Subject: [PATCH 15/15] chore(deps): update rust crate tauri to 2.6.1 (#3906) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src-tauri/Cargo.lock | 18 +++++++++--------- src-tauri/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 70c8dee1d4..b157437481 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" dependencies = [ "crossbeam-channel", "dpi", @@ -6834,16 +6834,16 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7a0f4019c80391d143ee26cd7cd1ed271ac241d3087d333f99f3269ba90812" +checksum = "773663ec28d911ac02f3a478c55f7f2fa581368d9e16ce9dff8d650b3666f91e" dependencies = [ "anyhow", "bytes", "dirs 6.0.0", "dunce", "embed_plist", - "getrandom 0.2.15", + "getrandom 0.3.3", "glob", "gtk", "heck 0.5.0", @@ -6936,9 +6936,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f59e1d1fa9651212dcb890a0c66226d819b716490b0cf43c078514da3591705" +checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -7866,9 +7866,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" dependencies = [ "crossbeam-channel", "dirs 6.0.0", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1e5b6d454f..98765ebac0 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -47,7 +47,7 @@ regex = "1.11.1" sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" } image = "0.25.6" imageproc = "0.25.0" -tauri = { version = "2.6.0", features = [ +tauri = { version = "2.6.1", features = [ "protocol-asset", "devtools", "tray-icon",