use crate::{ config::{Config, IVerge}, log_err, }; use anyhow::{anyhow, Result}; use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use std::env::current_exe; use std::sync::Arc; use sysproxy::{Autoproxy, Sysproxy}; use tauri::async_runtime::Mutex as TokioMutex; use tokio::time::{sleep, Duration}; pub struct Sysopt { update_sysproxy: Arc>, reset_sysproxy: Arc>, /// helps to auto launch the app auto_launch: Arc>>, /// record whether the guard async is running or not guard_state: Arc>, } #[cfg(target_os = "windows")] static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;"; #[cfg(target_os = "linux")] static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1"; #[cfg(target_os = "macos")] static DEFAULT_BYPASS: &str = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,"; fn get_bypass() -> String { let use_default = Config::verge().latest().use_default_bypass.unwrap_or(true); let res = { let verge = Config::verge(); let verge = verge.latest(); verge.system_proxy_bypass.clone() }; let custom_bypass = match res { Some(bypass) => bypass, None => "".to_string(), }; if custom_bypass.is_empty() { DEFAULT_BYPASS.to_string() } else if use_default { format!("{},{}", DEFAULT_BYPASS, custom_bypass) } else { custom_bypass } } impl Sysopt { pub fn global() -> &'static Sysopt { static SYSOPT: OnceCell = OnceCell::new(); SYSOPT.get_or_init(|| Sysopt { update_sysproxy: Arc::new(TokioMutex::new(false)), reset_sysproxy: Arc::new(TokioMutex::new(false)), auto_launch: Arc::new(Mutex::new(None)), guard_state: Arc::new(TokioMutex::new(false)), }) } pub fn init_guard_sysproxy(&self) -> Result<()> { self.guard_proxy(); Ok(()) } /// init the sysproxy pub async fn update_sysproxy(&self) -> Result<()> { let _ = self.update_sysproxy.lock(); let port = Config::verge() .latest() .verge_mixed_port .unwrap_or(Config::clash().data().get_mixed_port()); let pac_port = IVerge::get_singleton_port(); let (enable, pac) = { let verge = Config::verge(); let verge = verge.latest(); ( verge.enable_system_proxy.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false), ) }; #[cfg(not(target_os = "windows"))] { let mut sys = Sysproxy { enable, host: String::from("127.0.0.1"), port, bypass: get_bypass(), }; let mut auto = Autoproxy { enable, url: format!("http://127.0.0.1:{pac_port}/commands/pac"), }; if pac { sys.enable = false; auto.enable = true; sys.set_system_proxy()?; auto.set_auto_proxy()?; } else { auto.enable = false; sys.enable = true; auto.set_auto_proxy()?; sys.set_system_proxy()?; } } #[cfg(target_os = "windows")] { if !enable { return self.reset_sysproxy().await; } use crate::core::handle::Handle; use crate::utils::dirs; use anyhow::bail; use tauri_plugin_shell::ShellExt; let app_handle = Handle::global().app_handle().unwrap(); let binary_path = dirs::service_path()?; let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); if !sysproxy_exe.exists() { bail!("sysproxy.exe not found"); } let shell = app_handle.shell(); let output = if pac { let address = format!("http://{}:{}/pac", "127.0.0.1", pac_port); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["opac", address.as_str()]) .output() .await .unwrap(); output } else { let address = format!("{}:{}", "127.0.0.1", port); let bypass = get_bypass(); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["global", address.as_str(), bypass.as_ref()]) .output() .await .unwrap(); output }; if !output.status.success() { bail!("sysproxy exe run failed"); } } Ok(()) } /// reset the sysproxy pub async fn reset_sysproxy(&self) -> Result<()> { let _ = self.reset_sysproxy.lock(); //直接关闭所有代理 #[cfg(not(target_os = "windows"))] { let mut sysproxy: Sysproxy = Sysproxy::get_system_proxy()?; let mut autoproxy = Autoproxy::get_auto_proxy()?; sysproxy.enable = false; autoproxy.enable = false; autoproxy.set_auto_proxy()?; sysproxy.set_system_proxy()?; } #[cfg(target_os = "windows")] { use crate::core::handle::Handle; use crate::utils::dirs; use anyhow::bail; use tauri_plugin_shell::ShellExt; let app_handle = Handle::global().app_handle().unwrap(); let binary_path = dirs::service_path()?; let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); if !sysproxy_exe.exists() { bail!("sysproxy.exe not found"); } let shell = app_handle.shell(); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["set", "1"]) .output() .await .unwrap(); if !output.status.success() { bail!("sysproxy exe run failed"); } } Ok(()) } /// init the auto launch pub fn init_launch(&self) -> Result<()> { let app_exe = current_exe()?; // let app_exe = dunce::canonicalize(app_exe)?; let app_name = app_exe .file_stem() .and_then(|f| f.to_str()) .ok_or(anyhow!("failed to get file stem"))?; let app_path = app_exe .as_os_str() .to_str() .ok_or(anyhow!("failed to get app_path"))? .to_string(); // fix issue #26 #[cfg(target_os = "windows")] let app_path = format!("\"{app_path}\""); // use the /Applications/Clash Verge.app path #[cfg(target_os = "macos")] let app_path = (|| -> Option { let path = std::path::PathBuf::from(&app_path); let path = path.parent()?.parent()?.parent()?; let extension = path.extension()?.to_str()?; match extension == "app" { true => Some(path.as_os_str().to_str()?.to_string()), false => None, } })() .unwrap_or(app_path); #[cfg(target_os = "linux")] let app_path = { use crate::core::handle::Handle; use tauri::Manager; let app_handle = Handle::global().app_handle(); match app_handle { Some(app_handle) => { let appimage = app_handle.env().appimage; appimage .and_then(|p| p.to_str().map(|s| s.to_string())) .unwrap_or(app_path) } None => app_path, } }; let auto = AutoLaunchBuilder::new() .set_app_name(app_name) .set_app_path(&app_path) .build()?; *self.auto_launch.lock() = Some(auto); Ok(()) } /// update the startup pub fn update_launch(&self) -> Result<()> { let auto_launch = self.auto_launch.lock(); let enable = { Config::verge().latest().enable_auto_launch }; let enable = enable.unwrap_or(false); let auto_launch = auto_launch.as_ref().unwrap(); match enable { true => auto_launch.enable()?, false => log_err!(auto_launch.disable()), // 忽略关闭的错误 }; Ok(()) } fn guard_proxy(&self) { let _ = self.guard_state.lock(); tauri::async_runtime::spawn(async move { // default duration is 10s let mut wait_secs = 10u64; loop { sleep(Duration::from_secs(wait_secs)).await; let (enable, guard, guard_duration, pac) = { let verge = Config::verge(); let verge = verge.latest(); ( verge.enable_system_proxy.unwrap_or(false), verge.enable_proxy_guard.unwrap_or(false), verge.proxy_guard_duration.unwrap_or(10), verge.proxy_auto_config.unwrap_or(false), ) }; // stop loop if !enable || !guard { continue; } // update duration wait_secs = guard_duration; log::debug!(target: "app", "try to guard the system proxy"); let sysproxy = Sysproxy::get_system_proxy(); let autoproxy = Autoproxy::get_auto_proxy(); if sysproxy.is_err() || autoproxy.is_err() { log::error!(target: "app", "failed to get the system proxy"); continue; } let sysproxy_enable = sysproxy.ok().map(|s| s.enable).unwrap_or(false); let autoproxy_enable = autoproxy.ok().map(|s| s.enable).unwrap_or(false); if sysproxy_enable || autoproxy_enable { continue; } let port = { Config::verge() .latest() .verge_mixed_port .unwrap_or(Config::clash().data().get_mixed_port()) }; let pac_port = IVerge::get_singleton_port(); #[cfg(not(target_os = "windows"))] { if pac { let autoproxy = Autoproxy { enable: true, url: format!("http://127.0.0.1:{pac_port}/commands/pac"), }; log_err!(autoproxy.set_auto_proxy()); } else { let sysproxy = Sysproxy { enable: true, host: "127.0.0.1".into(), port, bypass: get_bypass(), }; log_err!(sysproxy.set_system_proxy()); } } // #[cfg(target_os = "windows")] { use crate::core::handle::Handle; use crate::utils::dirs; use tauri_plugin_shell::ShellExt; let app_handle = Handle::global().app_handle().unwrap(); let binary_path = dirs::service_path().unwrap(); let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); if !sysproxy_exe.exists() { break; } let shell = app_handle.shell(); let output = if pac { let address = format!("http://{}:{}/pac", "127.0.0.1", pac_port); shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["opac", address.as_str()]) .output() .await .unwrap() } else { let address = format!("{}:{}", "127.0.0.1", port); let bypass = get_bypass(); shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["global", address.as_str(), bypass.as_ref()]) .output() .await .unwrap() }; if !output.status.success() { break; } }; } }); } }