use crate::{ core::Clash, utils::{config, dirs, sysopt::SysProxyConfig}, }; use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tauri::{api::path::resource_dir, async_runtime::Mutex}; /// ### `verge.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct VergeConfig { /// `light` or `dark` pub theme_mode: Option, /// enable blur mode /// maybe be able to set the alpha pub theme_blur: Option, /// enable traffic graph default is true pub traffic_graph: Option, /// can the app auto startup pub enable_auto_launch: Option, /// set system proxy pub enable_system_proxy: Option, /// enable proxy guard pub enable_proxy_guard: Option, /// set system proxy bypass pub system_proxy_bypass: Option, } static VERGE_CONFIG: &str = "verge.yaml"; impl VergeConfig { pub fn new() -> Self { config::read_yaml::(dirs::app_home_dir().join(VERGE_CONFIG)) } /// Save Verge App Config pub fn save_file(&self) -> Result<(), String> { config::save_yaml( dirs::app_home_dir().join(VERGE_CONFIG), self, Some("# The Config for Clash Verge App\n\n"), ) } } /// Verge App abilities #[derive(Debug)] pub struct Verge { pub config: VergeConfig, pub old_sysproxy: Option, pub cur_sysproxy: Option, pub auto_launch: Option, /// record whether the guard async is running or not guard_state: Arc>, } impl Default for Verge { fn default() -> Self { Verge::new() } } impl Verge { pub fn new() -> Self { Verge { config: VergeConfig::new(), old_sysproxy: None, cur_sysproxy: None, auto_launch: None, guard_state: Arc::new(Mutex::new(false)), } } /// init the sysproxy pub fn init_sysproxy(&mut self, port: Option) { if let Some(port) = port { let enable = self.config.enable_system_proxy.clone().unwrap_or(false); self.old_sysproxy = match SysProxyConfig::get_sys() { Ok(proxy) => Some(proxy), Err(_) => None, }; let bypass = self.config.system_proxy_bypass.clone(); let sysproxy = SysProxyConfig::new(enable, port, bypass); if enable { if sysproxy.set_sys().is_err() { log::error!("failed to set system proxy"); } } self.cur_sysproxy = Some(sysproxy); } // launchs the system proxy guard Verge::guard_proxy(10, self.guard_state.clone()); } /// reset the sysproxy pub fn reset_sysproxy(&mut self) { if let Some(sysproxy) = self.old_sysproxy.take() { match sysproxy.set_sys() { Ok(_) => self.cur_sysproxy = None, Err(_) => log::error!("failed to reset proxy for"), } } } /// init the auto launch pub fn init_launch(&mut self, package_info: &tauri::PackageInfo) { let app_name = "clash-verge"; let app_path = get_app_path(app_name); let app_path = resource_dir(package_info, &tauri::Env::default()) .unwrap() .join(app_path); let app_path = app_path.as_os_str().to_str().unwrap(); let auto = AutoLaunchBuilder::new() .set_app_name(app_name) .set_app_path(app_path) .build(); self.auto_launch = Some(auto); } /// sync the startup when run the app pub fn sync_launch(&self) -> Result<(), String> { let enable = self.config.enable_auto_launch.clone().unwrap_or(false); if !enable { return Ok(()); } if self.auto_launch.is_none() { return Err("should init the auto launch first".into()); } let auto_launch = self.auto_launch.clone().unwrap(); let is_enabled = auto_launch.is_enabled().unwrap_or(false); if !is_enabled { if let Err(_) = auto_launch.enable() { return Err("failed to enable auto-launch".into()); } } Ok(()) } /// update the startup fn update_launch(&mut self, enable: bool) -> Result<(), String> { let conf_enable = self.config.enable_auto_launch.clone().unwrap_or(false); if enable == conf_enable { return Ok(()); } let auto_launch = self.auto_launch.clone().unwrap(); let result = match enable { true => auto_launch.enable(), false => auto_launch.disable(), }; match result { Ok(_) => Ok(()), Err(err) => { log::error!("{err}"); Err("failed to set system startup info".into()) } } } /// patch verge config /// There should be only one update at a time here /// so call the save_file at the end is savely pub fn patch_config(&mut self, patch: VergeConfig) -> Result<(), String> { // only change it if patch.theme_mode.is_some() { self.config.theme_mode = patch.theme_mode; } if patch.theme_blur.is_some() { self.config.theme_blur = patch.theme_blur; } if patch.traffic_graph.is_some() { self.config.traffic_graph = patch.traffic_graph; } // should update system startup if patch.enable_auto_launch.is_some() { let enable = patch.enable_auto_launch.unwrap(); self.update_launch(enable)?; self.config.enable_auto_launch = Some(enable); } // should update system proxy if patch.enable_system_proxy.is_some() { let enable = patch.enable_system_proxy.unwrap(); if let Some(mut sysproxy) = self.cur_sysproxy.take() { sysproxy.enable = enable; if sysproxy.set_sys().is_err() { self.cur_sysproxy = Some(sysproxy); log::error!("failed to set system proxy"); return Err("failed to set system proxy".into()); } self.cur_sysproxy = Some(sysproxy); } self.config.enable_system_proxy = Some(enable); } // should update system proxy too if patch.system_proxy_bypass.is_some() { let bypass = patch.system_proxy_bypass.unwrap(); if let Some(mut sysproxy) = self.cur_sysproxy.take() { if sysproxy.enable { sysproxy.bypass = bypass.clone(); if sysproxy.set_sys().is_err() { self.cur_sysproxy = Some(sysproxy); log::error!("failed to set system proxy"); return Err("failed to set system proxy".into()); } } self.cur_sysproxy = Some(sysproxy); } self.config.system_proxy_bypass = Some(bypass); } // proxy guard // only change it if patch.enable_proxy_guard.is_some() { self.config.enable_proxy_guard = patch.enable_proxy_guard; } // relaunch the guard if patch.enable_system_proxy.is_some() || patch.enable_proxy_guard.is_some() { Verge::guard_proxy(10, self.guard_state.clone()); } self.config.save_file() } } impl Verge { /// launch a system proxy guard /// read config from file directly pub fn guard_proxy(wait_secs: u64, guard_state: Arc>) { use tokio::time::{sleep, Duration}; tauri::async_runtime::spawn(async move { // if it is running, exit let mut state = guard_state.lock().await; if *state { return; } *state = true; std::mem::drop(state); loop { sleep(Duration::from_secs(wait_secs)).await; log::debug!("[Guard]: heartbeat detection"); let verge = Verge::new(); let enable_proxy = verge.config.enable_system_proxy.unwrap_or(false); let enable_guard = verge.config.enable_proxy_guard.unwrap_or(false); // stop loop if !enable_guard || !enable_proxy { break; } log::info!("[Guard]: try to guard proxy"); let clash = Clash::new(); match &clash.info.port { Some(port) => { let bypass = verge.config.system_proxy_bypass.clone(); let sysproxy = SysProxyConfig::new(true, port.clone(), bypass); if let Err(err) = sysproxy.set_sys() { log::error!("[Guard]: {err}"); log::error!("[Guard]: fail to set system proxy"); } } None => log::error!("[Guard]: fail to parse clash port"), } } let mut state = guard_state.lock().await; *state = false; }); } } // Get the target app_path fn get_app_path(app_name: &str) -> String { #[cfg(target_os = "linux")] let ext = ""; #[cfg(target_os = "macos")] let ext = ""; #[cfg(target_os = "windows")] let ext = ".exe"; String::from(app_name) + ext }