From d289a608d9ea7165cbbf209443a135e43fcfda14 Mon Sep 17 00:00:00 2001 From: wonfen Date: Mon, 14 Sep 2026 22:10:23 +0800 Subject: [PATCH] refactor(sysproxy): delegate macOS network monitoring to sysproxy-rs --- Cargo.lock | 6 +-- src-tauri/Cargo.toml | 2 - src-tauri/src/core/manager/lifecycle.rs | 5 +- src-tauri/src/core/network_watch.rs | 65 ++++++++++--------------- src-tauri/src/core/proxy_control.rs | 9 ++-- 5 files changed, 34 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae16a28f4..b6c53d9a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,7 +1140,6 @@ dependencies = [ "clash-verge-media-unlock", "clash-verge-signal", "clash_verge_service_ipc", - "core-foundation 0.9.4", "criterion", "dark-light", "deelevate", @@ -1175,7 +1174,6 @@ dependencies = [ "smartstring", "socket2 0.6.5", "sysproxy", - "system-configuration", "tauri", "tauri-build", "tauri-plugin-autostart", @@ -7015,8 +7013,8 @@ dependencies = [ [[package]] name = "sysproxy" -version = "0.7.0" -source = "git+https://github.com/clash-verge-rev/sysproxy-rs.git?branch=main#61118cab4878ce66b8cb12b467b682b493f20e6c" +version = "0.7.1" +source = "git+https://github.com/clash-verge-rev/sysproxy-rs.git?branch=main#44aaf00ec9c6779e5a461a55d882eddff7841c98" dependencies = [ "iptools", "log", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6e332e46c..d11aac03d 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -115,8 +115,6 @@ libc = "0.2.189" [target.'cfg(target_os = "macos")'.dependencies] tauri-plugin-wdio-webdriver = { version = "=1.3.0", optional = true } -core-foundation = "0.9" -system-configuration = "0.7" objc2 = "0.6.4" objc2-foundation = { version = "0.3.2", features = [ "NSString", diff --git a/src-tauri/src/core/manager/lifecycle.rs b/src-tauri/src/core/manager/lifecycle.rs index 8bb0f6cb8..0496859d2 100644 --- a/src-tauri/src/core/manager/lifecycle.rs +++ b/src-tauri/src/core/manager/lifecycle.rs @@ -476,10 +476,7 @@ impl CoreManager { // rather than fail — only if the watcher is actually live; a user toggling while // offline still fails fast. #[cfg(target_os = "macos")] - let watcher_armed = crate::core::network_watch::is_armed(); - #[cfg(not(target_os = "macos"))] - let watcher_armed = false; - if watcher_armed + if crate::core::network_watch::is_armed() && !crate::utils::resolve::is_resolve_done() && Config::verge() .await diff --git a/src-tauri/src/core/network_watch.rs b/src-tauri/src/core/network_watch.rs index 2da416cf0..1cfa36139 100644 --- a/src-tauri/src/core/network_watch.rs +++ b/src-tauri/src/core/network_watch.rs @@ -5,53 +5,35 @@ use crate::{config::Config, core::CoreManager, core::proxy_control, process::AsyncHandler}; use clash_verge_logging::{Type, logging}; -use core_foundation::{ - array::CFArray, - runloop::{CFRunLoop, kCFRunLoopCommonModes}, - string::CFString, -}; -use std::sync::atomic::{AtomicBool, Ordering}; -use system_configuration::dynamic_store::{SCDynamicStore, SCDynamicStoreBuilder, SCDynamicStoreCallBackContext}; +use parking_lot::Mutex; +use sysproxy::NetworkServiceMonitor; -/// Set once the subscription is live. Startup defers the proxy write to the watcher only then. -static ARMED: AtomicBool = AtomicBool::new(false); +static MONITOR: Mutex> = Mutex::new(None); pub fn is_armed() -> bool { - ARMED.load(Ordering::Acquire) + MONITOR.lock().is_some() } /// Subscribe to the primary IPv4 service on the main run loop. Armed on return, so a service /// appearing from here on is never missed. pub fn start() { - let store = SCDynamicStoreBuilder::new("clash-verge-rev") - .callback_context(SCDynamicStoreCallBackContext { - callout: on_change, - info: (), - }) - .build(); - let keys = CFArray::from_CFTypes(&[CFString::from_static_string("State:/Network/Global/IPv4")]); - let Some(source) = store - .filter(|store| store.set_notification_keys(&keys, &CFArray::::from_CFTypes(&[]))) - .and_then(|store| store.create_run_loop_source()) - else { - logging!( + match NetworkServiceMonitor::start(|| { + AsyncHandler::spawn(reapply); + }) { + Ok(monitor) => { + *MONITOR.lock() = Some(monitor); + logging!( + debug, + Type::Core, + "network watch armed; the system proxy is re-applied on network changes" + ); + } + Err(error) => logging!( warn, Type::Core, - "could not watch the network; the proxy is not re-applied on changes" - ); - return; - }; - CFRunLoop::get_main().add_source(&source, unsafe { kCFRunLoopCommonModes }); - ARMED.store(true, Ordering::Release); - logging!( - debug, - Type::Core, - "network watch armed; the system proxy is re-applied on network changes" - ); -} - -fn on_change(_: SCDynamicStore, _: CFArray, (): &mut ()) { - AsyncHandler::spawn(reapply); + "could not watch the network; the proxy is not re-applied on changes: {error}" + ), + } } async fn reapply() { @@ -66,10 +48,17 @@ async fn reapply() { .latest_arc() .enable_system_proxy .unwrap_or_default() - || !proxy_control::has_network_service().await { return; } + if !proxy_control::has_network_service().await { + logging!( + debug, + Type::Core, + "network changed without a service to write the proxy on; waiting for the next change" + ); + return; + } if let Err(error) = manager.apply_proxy_after_start().await { logging!( warn, diff --git a/src-tauri/src/core/proxy_control.rs b/src-tauri/src/core/proxy_control.rs index 538cc6dcf..0ec2949c8 100644 --- a/src-tauri/src/core/proxy_control.rs +++ b/src-tauri/src/core/proxy_control.rs @@ -385,12 +385,11 @@ async fn current_service_proxy_config(verge: &IVerge) -> Result bool { - tokio::task::spawn_blocking( - || !matches!(sysproxy::Sysproxy::get_system_proxy(), Err(error) if is_missing_network_service(&error)), - ) - .await - .unwrap_or(true) + tokio::task::spawn_blocking(|| sysproxy::Sysproxy::has_network_service().unwrap_or(true)) + .await + .unwrap_or(true) } pub fn is_reportable(error: &anyhow::Error) -> bool {