diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 4d1e7a8..0b6604d 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,12 +1,11 @@ use crate::{ core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, VergeConfig}, - utils::{dirs, sysopt::SysProxyConfig}, + utils::{dirs, help, sysopt::SysProxyConfig}, }; use crate::{log_if_err, ret_err, wrap_err}; use anyhow::Result; use serde_yaml::Mapping; -use std::process::Command; -use tauri::{api, Manager, State}; +use tauri::{api, State}; type CmdResult = Result; @@ -17,11 +16,10 @@ pub fn get_profiles(core: State<'_, Core>) -> CmdResult { Ok(profiles.clone()) } -/// synchronize data irregularly +/// manually exec enhanced profile #[tauri::command] -pub fn sync_profiles(core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock(); - wrap_err!(profiles.sync_file()) +pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult { + wrap_err!(core.activate_enhanced(false)) } /// import the profile from url @@ -102,23 +100,18 @@ pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult { drop(profiles); - log_if_err!(core.activate_enhanced(false)); - - Ok(()) + wrap_err!(core.activate_enhanced(false)) } /// change the profile chain #[tauri::command] pub fn change_profile_chain(chain: Option>, core: State<'_, Core>) -> CmdResult { - dbg!("change profile chain"); let mut profiles = core.profiles.lock(); profiles.put_chain(chain); - dbg!("change profile chain finish"); + drop(profiles); - log_if_err!(core.activate_enhanced(false)); - - Ok(()) + wrap_err!(core.activate_enhanced(false)) } /// change the profile valid fields @@ -126,18 +119,10 @@ pub fn change_profile_chain(chain: Option>, core: State<'_, Core>) - pub fn change_profile_valid(valid: Option>, core: State) -> CmdResult { let mut profiles = core.profiles.lock(); profiles.put_valid(valid); + drop(profiles); - log_if_err!(core.activate_enhanced(false)); - - Ok(()) -} - -/// manually exec enhanced profile -#[tauri::command] -pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult { - log_if_err!(core.activate_enhanced(false)); - Ok(()) + wrap_err!(core.activate_enhanced(false)) } /// delete profile item @@ -147,7 +132,7 @@ pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult { if wrap_err!(profiles.delete_item(index))? { drop(profiles); - // std::mem::drop(profiles); + log_if_err!(core.activate_enhanced(false)); } @@ -178,32 +163,7 @@ pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult { ret_err!("the file not found"); } - // use vscode first - if let Ok(code) = which::which("code") { - #[cfg(target_os = "windows")] - { - use std::os::windows::process::CommandExt; - - if let Err(err) = Command::new(code) - .creation_flags(0x08000000) - .arg(path) - .spawn() - { - log::error!("failed to open file by VScode for {err}"); - return Err("failed to open file by VScode".into()); - } - } - - #[cfg(not(target_os = "windows"))] - if let Err(err) = Command::new(code).arg(path).spawn() { - log::error!("failed to open file by VScode for {err}"); - return Err("failed to open file by VScode".into()); - } - - return Ok(()); - } - - wrap_err!(open::that(path)) + wrap_err!(help::open_file(path)) } /// read the profile item file data @@ -233,12 +193,6 @@ pub fn save_profile_file( wrap_err!(item.save_file(file_data.unwrap())) } -/// restart the sidecar -#[tauri::command] -pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult { - wrap_err!(core.restart_clash()) -} - /// get the clash core info from the state /// the caller can also get the infomation by clash's api #[tauri::command] @@ -255,29 +209,11 @@ pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult wrap_err!(core.patch_clash(payload)) } -/// get the system proxy -#[tauri::command] -pub fn get_sys_proxy() -> Result { - wrap_err!(SysProxyConfig::get_sys()) -} - -/// get the current proxy config -/// which may not the same as system proxy -#[tauri::command] -pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult> { - let verge = core.verge.lock(); - Ok(verge.cur_sysproxy.clone()) -} - /// get the verge config #[tauri::command] pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { let verge = core.verge.lock(); - let mut config = verge.config.clone(); - - if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() { - config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass) - } + let config = verge.config.clone(); Ok(config) } @@ -290,41 +226,35 @@ pub fn patch_verge_config( app_handle: tauri::AppHandle, core: State<'_, Core>, ) -> Result<(), String> { - let tun_mode = payload.enable_tun_mode.clone(); - let system_proxy = payload.enable_system_proxy.clone(); + wrap_err!(core.patch_verge(payload, &app_handle)) +} - let mut verge = core.verge.lock(); - wrap_err!(verge.patch_config(payload))?; - - // change system tray - if system_proxy.is_some() || tun_mode.is_some() { - verge.update_systray(&app_handle).unwrap(); - } - - // change tun mode - if tun_mode.is_some() { - #[cfg(target_os = "windows")] - if *tun_mode.as_ref().unwrap() { - let wintun_dll = dirs::app_home_dir().join("wintun.dll"); - if !wintun_dll.exists() { - log::error!("failed to enable TUN for missing `wintun.dll`"); - return Err("failed to enable TUN for missing `wintun.dll`".into()); - } - } - - std::mem::drop(verge); - log_if_err!(core.activate_enhanced(false)); - } - - Ok(()) +/// restart the sidecar +#[tauri::command] +pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult { + wrap_err!(core.restart_clash()) } /// kill all sidecars when update app #[tauri::command] -pub fn kill_sidecars() { +pub fn kill_sidecar() { api::process::kill_children(); } +/// get the system proxy +#[tauri::command] +pub fn get_sys_proxy() -> Result { + wrap_err!(SysProxyConfig::get_sys()) +} + +/// get the current proxy config +/// which may not the same as system proxy +#[tauri::command] +pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult> { + let sysopt = core.sysopt.lock(); + wrap_err!(sysopt.get_sysproxy()) +} + /// open app config dir #[tauri::command] pub fn open_app_dir() -> Result<(), String> { diff --git a/src-tauri/src/core/clash copy.rs b/src-tauri/src/core/clash copy.rs deleted file mode 100644 index 463ab14..0000000 --- a/src-tauri/src/core/clash copy.rs +++ /dev/null @@ -1,520 +0,0 @@ -use super::{PrfEnhancedResult, Profiles, Verge, VergeConfig}; -use crate::log_if_err; -use crate::utils::{config, dirs, help}; -use anyhow::{bail, Result}; -use reqwest::header::HeaderMap; -use serde::{Deserialize, Serialize}; -use serde_yaml::{Mapping, Value}; -use std::{collections::HashMap, time::Duration}; -use tauri::api::process::{Command, CommandChild, CommandEvent}; -use tauri::Window; -use tokio::time::sleep; - -#[derive(Default, Debug, Clone, Deserialize, Serialize)] -pub struct ClashInfo { - /// clash sidecar status - pub status: String, - - /// clash core port - pub port: Option, - - /// same as `external-controller` - pub server: Option, - - /// clash secret - pub secret: Option, -} - -pub struct Clash { - /// maintain the clash config - pub config: Mapping, - - /// some info - pub info: ClashInfo, - - /// clash sidecar - pub sidecar: Option, - - /// save the main window - pub window: Option, -} - -impl Clash { - pub fn new() -> Clash { - let config = Clash::read_config(); - let info = Clash::get_info(&config); - - Clash { - config, - info, - sidecar: None, - window: None, - } - } - - /// get clash config - fn read_config() -> Mapping { - config::read_yaml::(dirs::clash_path()) - } - - /// save the clash config - fn save_config(&self) -> Result<()> { - config::save_yaml( - dirs::clash_path(), - &self.config, - Some("# Default Config For Clash Core\n\n"), - ) - } - - /// parse the clash's config.yaml - /// get some information - fn get_info(clash_config: &Mapping) -> ClashInfo { - let key_port_1 = Value::from("port"); - let key_port_2 = Value::from("mixed-port"); - let key_server = Value::from("external-controller"); - let key_secret = Value::from("secret"); - - let port = match clash_config.get(&key_port_1) { - Some(value) => match value { - Value::String(val_str) => Some(val_str.clone()), - Value::Number(val_num) => Some(val_num.to_string()), - _ => None, - }, - _ => None, - }; - let port = match port { - Some(_) => port, - None => match clash_config.get(&key_port_2) { - Some(value) => match value { - Value::String(val_str) => Some(val_str.clone()), - Value::Number(val_num) => Some(val_num.to_string()), - _ => None, - }, - _ => None, - }, - }; - - let server = match clash_config.get(&key_server) { - Some(value) => match value { - Value::String(val_str) => { - // `external-controller` could be - // "127.0.0.1:9090" or ":9090" - // Todo: maybe it could support single port - let server = val_str.clone(); - let server = match server.starts_with(":") { - true => format!("127.0.0.1{server}"), - false => server, - }; - - Some(server) - } - _ => None, - }, - _ => None, - }; - let secret = match clash_config.get(&key_secret) { - Some(value) => match value { - Value::String(val_str) => Some(val_str.clone()), - Value::Bool(val_bool) => Some(val_bool.to_string()), - Value::Number(val_num) => Some(val_num.to_string()), - _ => None, - }, - _ => None, - }; - - ClashInfo { - status: "init".into(), - port, - server, - secret, - } - } - - /// save the main window - pub fn set_window(&mut self, win: Option) { - self.window = win; - } - - /// run clash sidecar - pub fn run_sidecar(&mut self, profiles: &Profiles, delay: bool) -> Result<()> { - let app_dir = dirs::app_home_dir(); - let app_dir = app_dir.as_os_str().to_str().unwrap(); - - let cmd = Command::new_sidecar("clash")?; - let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?; - - self.sidecar = Some(cmd_child); - - // clash log - tauri::async_runtime::spawn(async move { - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Stdout(line) => log::info!("[clash]: {}", line), - CommandEvent::Stderr(err) => log::error!("[clash]: {}", err), - _ => {} - } - } - }); - - // activate profile - log_if_err!(self.activate(&profiles)); - log_if_err!(self.activate_enhanced(&profiles, delay, true)); - - Ok(()) - } - - /// drop clash sidecar - pub fn drop_sidecar(&mut self) -> Result<()> { - if let Some(sidecar) = self.sidecar.take() { - sidecar.kill()?; - } - Ok(()) - } - - /// restart clash sidecar - /// should reactivate profile after restart - pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> { - self.update_config(); - self.drop_sidecar()?; - self.run_sidecar(profiles, false) - } - - /// update the clash info - pub fn update_config(&mut self) { - self.config = Clash::read_config(); - self.info = Clash::get_info(&self.config); - } - - /// patch update the clash config - pub fn patch_config( - &mut self, - patch: Mapping, - verge: &mut Verge, - profiles: &mut Profiles, - ) -> Result<()> { - let mix_port_key = Value::from("mixed-port"); - let mut port = None; - - for (key, value) in patch.into_iter() { - let value = value.clone(); - - // check whether the mix_port is changed - if key == mix_port_key { - if value.is_number() { - port = value.as_i64().as_ref().map(|n| n.to_string()); - } else { - port = value.as_str().as_ref().map(|s| s.to_string()); - } - } - - self.config.insert(key.clone(), value); - } - - self.save_config()?; - - if let Some(port) = port { - self.restart_sidecar(profiles)?; - verge.init_sysproxy(Some(port)); - } - - Ok(()) - } - - /// revise the `tun` and `dns` config - fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping { - macro_rules! revise { - ($map: expr, $key: expr, $val: expr) => { - let ret_key = Value::String($key.into()); - $map.insert(ret_key, Value::from($val)); - }; - } - - // if key not exists then append value - macro_rules! append { - ($map: expr, $key: expr, $val: expr) => { - let ret_key = Value::String($key.into()); - if !$map.contains_key(&ret_key) { - $map.insert(ret_key, Value::from($val)); - } - }; - } - - // tun config - let tun_val = config.get(&Value::from("tun")); - let mut new_tun = Mapping::new(); - - if tun_val.is_some() && tun_val.as_ref().unwrap().is_mapping() { - new_tun = tun_val.as_ref().unwrap().as_mapping().unwrap().clone(); - } - - revise!(new_tun, "enable", enable); - - if enable { - append!(new_tun, "stack", "gvisor"); - append!(new_tun, "dns-hijack", vec!["198.18.0.2:53"]); - append!(new_tun, "auto-route", true); - append!(new_tun, "auto-detect-interface", true); - } - - revise!(config, "tun", new_tun); - - // dns config - let dns_val = config.get(&Value::from("dns")); - let mut new_dns = Mapping::new(); - - if dns_val.is_some() && dns_val.as_ref().unwrap().is_mapping() { - new_dns = dns_val.as_ref().unwrap().as_mapping().unwrap().clone(); - } - - // 借鉴cfw的默认配置 - revise!(new_dns, "enable", enable); - - if enable { - append!(new_dns, "enhanced-mode", "fake-ip"); - append!( - new_dns, - "nameserver", - vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"] - ); - append!(new_dns, "fallback", vec![] as Vec<&str>); - - #[cfg(target_os = "windows")] - append!( - new_dns, - "fake-ip-filter", - vec![ - "dns.msftncsi.com", - "www.msftncsi.com", - "www.msftconnecttest.com" - ] - ); - } - - revise!(config, "dns", new_dns); - config - } - - /// activate the profile - /// generate a new profile to the temp_dir - /// then put the path to the clash core - fn _activate(info: ClashInfo, config: Mapping, window: Option) -> Result<()> { - let verge_config = VergeConfig::new(); - let tun_enable = verge_config.enable_tun_mode.unwrap_or(false); - - let config = Clash::_tun_mode(config, tun_enable); - - let temp_path = dirs::profiles_temp_path(); - config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?; - - tauri::async_runtime::spawn(async move { - if info.server.is_none() { - return; - } - - let server = info.server.unwrap(); - let server = format!("http://{server}/configs"); - - let mut headers = HeaderMap::new(); - headers.insert("Content-Type", "application/json".parse().unwrap()); - - if let Some(secret) = info.secret.as_ref() { - let secret = format!("Bearer {}", secret.clone()).parse().unwrap(); - headers.insert("Authorization", secret); - } - - let mut data = HashMap::new(); - data.insert("path", temp_path.as_os_str().to_str().unwrap()); - - // retry 5 times - for _ in 0..5 { - match reqwest::ClientBuilder::new().no_proxy().build() { - Ok(client) => { - let builder = client.put(&server).headers(headers.clone()).json(&data); - - match builder.send().await { - Ok(resp) => { - if resp.status() != 204 { - log::error!("failed to activate clash for status \"{}\"", resp.status()); - } - - // emit the window to update something - if let Some(window) = window { - window.emit("verge://refresh-clash-config", "yes").unwrap(); - } - - // do not retry - break; - } - Err(err) => log::error!("failed to activate for `{err}`"), - } - } - Err(err) => log::error!("failed to activate for `{err}`"), - } - sleep(Duration::from_millis(500)).await; - } - }); - - Ok(()) - } - - /// enhanced profiles mode - /// - (sync) refresh config if enhance chain is null - /// - (async) enhanced config - pub fn activate_enhanced(&self, profiles: &Profiles, delay: bool, skip: bool) -> Result<()> { - if self.window.is_none() { - bail!("failed to get the main window"); - } - - let event_name = help::get_uid("e"); - let event_name = format!("enhanced-cb-{event_name}"); - - // generate the payload - let payload = profiles.gen_enhanced(event_name.clone())?; - - let info = self.info.clone(); - - // do not run enhanced - if payload.chain.len() == 0 { - if skip { - return Ok(()); - } - - let mut config = self.config.clone(); - let filter_data = Clash::strict_filter(payload.current); - - for (key, value) in filter_data.into_iter() { - config.insert(key, value); - } - - return Clash::_activate(info, config, self.window.clone()); - } - - let window = self.window.clone().unwrap(); - let window_move = self.window.clone(); - - window.once(&event_name, move |event| { - if let Some(result) = event.payload() { - let result: PrfEnhancedResult = serde_json::from_str(result).unwrap(); - - if let Some(data) = result.data { - let mut config = Clash::read_config(); - let filter_data = Clash::loose_filter(data); // loose filter - - for (key, value) in filter_data.into_iter() { - config.insert(key, value); - } - - log_if_err!(Clash::_activate(info, config, window_move)); - log::info!("profile enhanced status {}", result.status); - } - - result.error.map(|err| log::error!("{err}")); - } - }); - - tauri::async_runtime::spawn(async move { - // wait the window setup during resolve app - if delay { - sleep(Duration::from_secs(2)).await; - } - window.emit("script-handler", payload).unwrap(); - }); - - Ok(()) - } - - /// activate the profile - /// auto activate enhanced profile - pub fn activate(&self, profiles: &Profiles) -> Result<()> { - let data = profiles.gen_activate()?; - let data = Clash::strict_filter(data); - - let info = self.info.clone(); - let mut config = self.config.clone(); - - for (key, value) in data.into_iter() { - config.insert(key, value); - } - - Clash::_activate(info, config, self.window.clone()) - } - - /// only 5 default fields available (clash config fields) - /// convert to lowercase - fn strict_filter(config: Mapping) -> Mapping { - // Only the following fields are allowed: - // proxies/proxy-providers/proxy-groups/rule-providers/rules - let valid_keys = vec![ - "proxies", - "proxy-providers", - "proxy-groups", - "rules", - "rule-providers", - ]; - - let mut new_config = Mapping::new(); - - for (key, value) in config.into_iter() { - key.as_str().map(|key_str| { - // change to lowercase - let mut key_str = String::from(key_str); - key_str.make_ascii_lowercase(); - - // filter - if valid_keys.contains(&&*key_str) { - new_config.insert(Value::String(key_str), value); - } - }); - } - - new_config - } - - /// more clash config fields available - /// convert to lowercase - fn loose_filter(config: Mapping) -> Mapping { - // all of these can not be revised by script or merge - // http/https/socks port should be under control - let not_allow = vec![ - "port", - "socks-port", - "mixed-port", - "allow-lan", - "mode", - "external-controller", - "secret", - "log-level", - ]; - - let mut new_config = Mapping::new(); - - for (key, value) in config.into_iter() { - key.as_str().map(|key_str| { - // change to lowercase - let mut key_str = String::from(key_str); - key_str.make_ascii_lowercase(); - - // filter - if !not_allow.contains(&&*key_str) { - new_config.insert(Value::String(key_str), value); - } - }); - } - - new_config - } -} - -impl Default for Clash { - fn default() -> Self { - Clash::new() - } -} - -impl Drop for Clash { - fn drop(&mut self) { - if let Err(err) = self.drop_sidecar() { - log::error!("{err}"); - } - } -} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index fb50efe..c15179a 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -1,14 +1,15 @@ use self::notice::Notice; use self::service::Service; +use self::sysopt::Sysopt; use crate::core::enhance::PrfEnhancedResult; use crate::log_if_err; -use crate::utils::help; +use crate::utils::{dirs, help}; use anyhow::{bail, Result}; use parking_lot::Mutex; use serde_yaml::Mapping; use std::sync::Arc; use std::time::Duration; -use tauri::Window; +use tauri::{AppHandle, Manager, Window}; use tokio::time::sleep; mod clash; @@ -17,6 +18,7 @@ mod notice; mod prfitem; mod profiles; mod service; +mod sysopt; mod timer; mod verge; @@ -35,6 +37,8 @@ pub struct Core { pub service: Arc>, + pub sysopt: Arc>, + pub window: Arc>>, } @@ -50,10 +54,12 @@ impl Core { verge: Arc::new(Mutex::new(verge)), profiles: Arc::new(Mutex::new(profiles)), service: Arc::new(Mutex::new(service)), + sysopt: Arc::new(Mutex::new(Sysopt::new())), window: Arc::new(Mutex::new(None)), } } + /// initialize the core state pub fn init(&self, app_handle: tauri::AppHandle) { let mut service = self.service.lock(); log_if_err!(service.start()); @@ -62,26 +68,30 @@ impl Core { log_if_err!(self.activate()); let clash = self.clash.lock(); - let mut verge = self.verge.lock(); + let verge = self.verge.lock(); - let hide = verge.config.enable_silent_start.clone().unwrap_or(false); + let silent_start = verge.config.enable_silent_start.clone(); + let auto_launch = verge.config.enable_auto_launch.clone(); // silent start - if hide { + if silent_start.unwrap_or(false) { let window = self.window.lock(); window.as_ref().map(|win| { win.hide().unwrap(); }); } - verge.init_sysproxy(clash.info.port.clone()); + let mut sysopt = self.sysopt.lock(); - log_if_err!(verge.init_launch()); - log_if_err!(verge.update_systray(&app_handle)); + sysopt.init_sysproxy(clash.info.port.clone(), &verge); drop(clash); drop(verge); + log_if_err!(sysopt.init_launch(auto_launch)); + + log_if_err!(self.update_systray(&app_handle)); + // wait the window setup during resolve app let core = self.clone(); tauri::async_runtime::spawn(async move { @@ -106,6 +116,7 @@ impl Core { self.activate_enhanced(true) } + /// Patch Clash /// handle the clash config changed pub fn patch_clash(&self, patch: Mapping) -> Result<()> { let (changed, port) = { @@ -123,13 +134,85 @@ impl Core { self.activate()?; self.activate_enhanced(true)?; - let mut verge = self.verge.lock(); - verge.init_sysproxy(port); + let mut sysopt = self.sysopt.lock(); + let verge = self.verge.lock(); + sysopt.init_sysproxy(port, &verge); } Ok(()) } + /// Patch Verge + pub fn patch_verge(&self, patch: VergeConfig, app_handle: &AppHandle) -> Result<()> { + let tun_mode = patch.enable_tun_mode.clone(); + let auto_launch = patch.enable_auto_launch.clone(); + let system_proxy = patch.enable_system_proxy.clone(); + let proxy_bypass = patch.system_proxy_bypass.clone(); + let proxy_guard = patch.enable_proxy_guard.clone(); + + if auto_launch.is_some() { + let mut sysopt = self.sysopt.lock(); + sysopt.update_launch(auto_launch)?; + } + + if system_proxy.is_some() || proxy_bypass.is_some() { + let mut sysopt = self.sysopt.lock(); + sysopt.update_sysproxy(system_proxy.clone(), proxy_bypass)?; + sysopt.guard_proxy(); + } + + if proxy_guard.unwrap_or(false) { + let sysopt = self.sysopt.lock(); + sysopt.guard_proxy(); + } + + #[cfg(target_os = "windows")] + if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) { + let wintun_dll = dirs::app_home_dir().join("wintun.dll"); + if !wintun_dll.exists() { + bail!("failed to enable TUN for missing `wintun.dll`"); + } + } + + // save the patch + let mut verge = self.verge.lock(); + verge.patch_config(patch)?; + drop(verge); + + if system_proxy.is_some() || tun_mode.is_some() { + self.update_systray(app_handle)?; + } + + if tun_mode.is_some() { + self.activate_enhanced(false)?; + } + + Ok(()) + } + + /// update the system tray state + pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> { + let verge = self.verge.lock(); + let tray = app_handle.tray_handle(); + + let system_proxy = verge.config.enable_system_proxy.as_ref(); + let tun_mode = verge.config.enable_tun_mode.as_ref(); + + tray + .get_item("system_proxy") + .set_selected(*system_proxy.unwrap_or(&false))?; + tray + .get_item("tun_mode") + .set_selected(*tun_mode.unwrap_or(&false))?; + + // update verge config + let window = app_handle.get_window("main"); + let notice = Notice::from(window); + notice.refresh_verge(); + + Ok(()) + } + /// activate the profile /// auto activate enhanced profile pub fn activate(&self) -> Result<()> { @@ -165,6 +248,7 @@ impl Core { service.set_config(info, config, notice) } + /// Enhanced /// enhanced profiles mode pub fn activate_enhanced(&self, skip: bool) -> Result<()> { let window = self.window.lock(); @@ -235,10 +319,6 @@ impl Core { result.error.map(|err| log::error!("{err}")); }); - // if delay { - // sleep(Duration::from_secs(2)).await; - // } - window.emit("script-handler", payload).unwrap(); Ok(()) diff --git a/src-tauri/src/core/prfitem.rs b/src-tauri/src/core/prfitem.rs index 3d99906..48f5a6c 100644 --- a/src-tauri/src/core/prfitem.rs +++ b/src-tauri/src/core/prfitem.rs @@ -60,7 +60,7 @@ pub struct PrfExtra { pub expire: usize, } -#[derive(Default, Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct PrfOption { /// for `remote` profile's http request /// see issue #13 diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs index af37f9c..fe00c50 100644 --- a/src-tauri/src/core/profiles.rs +++ b/src-tauri/src/core/profiles.rs @@ -47,6 +47,7 @@ impl Profiles { profiles.items = Some(vec![]); } + // compatiable with the old old old version profiles.items.as_mut().map(|items| { for mut item in items.iter_mut() { if item.uid.is_none() { @@ -67,19 +68,6 @@ impl Profiles { ) } - /// sync the config between file and memory - pub fn sync_file(&mut self) -> Result<()> { - let data = Self::read_file(); - if data.current.is_none() && data.items.is_none() { - bail!("failed to read profiles.yaml"); - } - - self.current = data.current; - self.chain = data.chain; - self.items = data.items; - Ok(()) - } - /// get the current uid pub fn get_current(&self) -> Option { self.current.clone() @@ -94,11 +82,9 @@ impl Profiles { let items = self.items.as_ref().unwrap(); let some_uid = Some(uid.clone()); - for each in items.iter() { - if each.uid == some_uid { - self.current = some_uid; - return self.save_file(); - } + if items.iter().find(|&each| each.uid == some_uid).is_some() { + self.current = some_uid; + return self.save_file(); } bail!("invalid uid \"{uid}\""); @@ -162,7 +148,7 @@ impl Profiles { self.save_file() } - /// update the item's value + /// update the item value pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> { let mut items = self.items.take().unwrap_or(vec![]); @@ -197,7 +183,7 @@ impl Profiles { // find the item let _ = self.get_item(&uid)?; - self.items.as_mut().map(|items| { + if let Some(items) = self.items.as_mut() { let some_uid = Some(uid.clone()); for mut each in items.iter_mut() { @@ -217,15 +203,15 @@ impl Profiles { let path = dirs::app_profiles_dir().join(&file); fs::File::create(path) - .unwrap() + .context(format!("failed to create file \"{}\"", file))? .write(file_data.as_bytes()) - .unwrap(); + .context(format!("failed to write to file \"{}\"", file))?; } break; } } - }); + } self.save_file() } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index b1eb42e..468037e 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -57,6 +57,8 @@ impl Service { self.start() } + /// update clash config + /// using PUT methods pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> { if self.sidecar.is_none() { bail!("did not start sidecar"); diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs new file mode 100644 index 0000000..8a6d7a8 --- /dev/null +++ b/src-tauri/src/core/sysopt.rs @@ -0,0 +1,207 @@ +use super::{Clash, Verge}; +use crate::{log_if_err, utils::sysopt::SysProxyConfig}; +use anyhow::{bail, Result}; +use auto_launch::{AutoLaunch, AutoLaunchBuilder}; +// use parking_lot::Mutex; +use std::sync::Arc; +use tauri::{async_runtime::Mutex, utils::platform::current_exe}; + +pub struct Sysopt { + /// current system proxy setting + cur_sysproxy: Option, + + /// record the original system proxy + /// recover it when exit + old_sysproxy: Option, + + /// helps to auto launch the app + auto_launch: Option, + + /// record whether the guard async is running or not + guard_state: Arc>, +} + +impl Sysopt { + pub fn new() -> Sysopt { + Sysopt { + cur_sysproxy: None, + old_sysproxy: None, + auto_launch: None, + guard_state: Arc::new(Mutex::new(false)), + } + } + + /// init the sysproxy + pub fn init_sysproxy(&mut self, port: Option, verge: &Verge) { + if let Some(port) = port { + let enable = verge.config.enable_system_proxy.clone().unwrap_or(false); + + self.old_sysproxy = match SysProxyConfig::get_sys() { + Ok(proxy) => Some(proxy), + Err(_) => None, + }; + + let bypass = verge.config.system_proxy_bypass.clone(); + let sysproxy = SysProxyConfig::new(enable, port, bypass); + + if enable { + if let Err(err) = sysproxy.set_sys() { + log::error!("failed to set system proxy for `{err}`"); + } + } + + self.cur_sysproxy = Some(sysproxy); + } + + // launchs the system proxy guard + self.guard_proxy(); + } + + /// update the system proxy + /// when the verge config is changed + pub fn update_sysproxy(&mut self, enable: Option, bypass: Option) -> Result<()> { + let sysproxy = self.cur_sysproxy.take(); + + if sysproxy.is_none() { + bail!("unhandle error for sysproxy is none"); + } + + let mut sysproxy = sysproxy.unwrap(); + + if let Some(enable) = enable { + sysproxy.enable = enable; + } + + if let Some(bypass) = bypass { + sysproxy.bypass = bypass; + } + + self.cur_sysproxy = Some(sysproxy); + + if self.cur_sysproxy.as_ref().unwrap().set_sys().is_err() { + bail!("failed to set system proxy"); + } + + Ok(()) + } + + /// 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"), + } + } + } + + /// get current proxy + pub fn get_sysproxy(&self) -> Result> { + Ok(self.cur_sysproxy.clone()) + } + + /// init the auto launch + pub fn init_launch(&mut self, enable: Option) -> Result<()> { + let app_exe = current_exe().unwrap(); + let app_exe = dunce::canonicalize(app_exe).unwrap(); + let app_name = app_exe.file_stem().unwrap().to_str().unwrap(); + let app_path = app_exe.as_os_str().to_str().unwrap(); + + // fix issue #26 + #[cfg(target_os = "windows")] + let app_path = format!("\"{app_path}\""); + #[cfg(target_os = "windows")] + let app_path = app_path.as_str(); + + let auto = AutoLaunchBuilder::new() + .set_app_name(app_name) + .set_app_path(app_path) + .build(); + + if let Some(enable) = enable { + // fix issue #26 + if enable { + auto.enable()?; + } + } + + self.auto_launch = Some(auto); + + Ok(()) + } + + /// update the startup + pub fn update_launch(&mut self, enable: Option) -> Result<()> { + if enable.is_none() { + return Ok(()); + } + + let enable = enable.unwrap(); + let auto_launch = self.auto_launch.as_ref().unwrap(); + + match enable { + true => auto_launch.enable()?, + false => auto_launch.disable()?, + }; + + Ok(()) + } + + /// launch a system proxy guard + /// read config from file directly + pub fn guard_proxy(&self) { + use tokio::time::{sleep, Duration}; + + let guard_state = self.guard_state.clone(); + + tauri::async_runtime::spawn(async move { + // if it is running, exit + let mut state = guard_state.lock().await; + if *state { + return; + } + *state = true; + drop(state); + + // default duration is 10s + let mut wait_secs = 10u64; + + 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); + let guard_duration = verge.config.proxy_guard_duration.unwrap_or(10); + + // update duration + wait_secs = guard_duration; + + // stop loop + if !enable_guard || !enable_proxy { + break; + } + + log::info!("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); + + log_if_err!(sysproxy.set_sys()); + } + None => log::error!("failed to parse clash port"), + } + } + + let mut state = guard_state.lock().await; + *state = false; + }); + } +} diff --git a/src-tauri/src/core/verge.rs b/src-tauri/src/core/verge.rs index eaa789c..f96b40f 100644 --- a/src-tauri/src/core/verge.rs +++ b/src-tauri/src/core/verge.rs @@ -1,14 +1,7 @@ use crate::log_if_err; -use crate::{ - core::Clash, - utils::{config, dirs, sysopt::SysProxyConfig}, -}; +use crate::utils::{config, dirs}; use anyhow::{bail, Result}; -use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tauri::{async_runtime::Mutex, utils::platform::current_exe}; -use tauri::{AppHandle, Manager}; /// ### `verge.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -87,19 +80,6 @@ impl VergeConfig { pub struct Verge { /// manage the verge config pub config: VergeConfig, - - /// current system proxy setting - pub cur_sysproxy: Option, - - /// record the original system proxy - /// recover it when exit - old_sysproxy: Option, - - /// helps to auto launch the app - auto_launch: Option, - - /// record whether the guard async is running or not - guard_state: Arc>, } impl Default for Verge { @@ -112,100 +92,11 @@ 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(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"), - } - } - } - - /// init the auto launch - pub fn init_launch(&mut self) -> Result<()> { - let app_exe = current_exe().unwrap(); - let app_exe = dunce::canonicalize(app_exe).unwrap(); - let app_name = app_exe.file_stem().unwrap().to_str().unwrap(); - let app_path = app_exe.as_os_str().to_str().unwrap(); - - // fix issue #26 - #[cfg(target_os = "windows")] - let app_path = format!("\"{app_path}\""); - #[cfg(target_os = "windows")] - let app_path = app_path.as_str(); - - let auto = AutoLaunchBuilder::new() - .set_app_name(app_name) - .set_app_path(app_path) - .build(); - - if let Some(enable) = self.config.enable_auto_launch.as_ref() { - // fix issue #26 - if *enable { - auto.enable()?; - } - } - - self.auto_launch = Some(auto); - - Ok(()) - } - - /// update the startup - fn update_launch(&mut self, enable: bool) -> Result<()> { - 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(); - - match enable { - true => auto_launch.enable()?, - false => auto_launch.disable()?, - }; - - Ok(()) - } - /// patch verge config - /// There should be only one update at a time here - /// so call the save_file at the end is savely + /// only save to file pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> { // only change it if patch.language.is_some() { @@ -217,62 +108,28 @@ impl Verge { 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; - } - if patch.enable_silent_start.is_some() { - self.config.enable_silent_start = patch.enable_silent_start; - } if patch.theme_setting.is_some() { self.config.theme_setting = patch.theme_setting; } + if patch.traffic_graph.is_some() { + self.config.traffic_graph = patch.traffic_graph; + } - // should update system startup + // system setting + if patch.enable_silent_start.is_some() { + self.config.enable_silent_start = patch.enable_silent_start; + } 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); + self.config.enable_auto_launch = patch.enable_auto_launch; } - // should update system proxy + // 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); - - bail!("failed to set system proxy"); - } - self.cur_sysproxy = Some(sysproxy); - } - self.config.enable_system_proxy = Some(enable); + self.config.enable_system_proxy = patch.enable_system_proxy; } - - // 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); - - bail!("failed to set system proxy"); - } - } - - self.cur_sysproxy = Some(sysproxy); - } - - self.config.system_proxy_bypass = Some(bypass); + self.config.system_proxy_bypass = patch.system_proxy_bypass; } - - // proxy guard - // only change it if patch.enable_proxy_guard.is_some() { self.config.enable_proxy_guard = patch.enable_proxy_guard; } @@ -280,103 +137,11 @@ impl Verge { self.config.proxy_guard_duration = patch.proxy_guard_duration; } - // relaunch the guard - if patch.enable_system_proxy.is_some() || patch.enable_proxy_guard.is_some() { - Verge::guard_proxy(self.guard_state.clone()); - } - - // handle the tun mode + // tun mode if patch.enable_tun_mode.is_some() { self.config.enable_tun_mode = patch.enable_tun_mode; } self.config.save_file() } - - /// update the system tray state - pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> { - // system proxy - let system_proxy = self.config.enable_system_proxy.as_ref(); - system_proxy.map(|system_proxy| { - app_handle - .tray_handle() - .get_item("system_proxy") - .set_selected(*system_proxy) - .unwrap(); - }); - - // tun mode - let tun_mode = self.config.enable_tun_mode.as_ref(); - tun_mode.map(|tun_mode| { - app_handle - .tray_handle() - .get_item("tun_mode") - .set_selected(*tun_mode) - .unwrap(); - }); - - // update verge config - let window = app_handle.get_window("main").unwrap(); - window.emit("verge://refresh-verge-config", "yes").unwrap(); - - Ok(()) - } -} - -impl Verge { - /// launch a system proxy guard - /// read config from file directly - pub fn guard_proxy(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); - - // default duration is 10s - let mut wait_secs = 10u64; - - 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); - let guard_duration = verge.config.proxy_guard_duration.unwrap_or(10); - - // update duration - wait_secs = guard_duration; - - // stop loop - if !enable_guard || !enable_proxy { - break; - } - - log::info!("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); - - log_if_err!(sysproxy.set_sys()); - } - None => log::error!("fail to parse clash port"), - } - } - - let mut state = guard_state.lock().await; - *state = false; - }); - } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 97b777e..9e0e0a8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -51,33 +51,33 @@ fn main() -> std::io::Result<()> { } "system_proxy" => { let core = app_handle.state::(); - let mut verge = core.verge.lock(); - let old_value = verge.config.enable_system_proxy.clone().unwrap_or(false); - let new_value = !old_value; + let new_value = { + let verge = core.verge.lock(); + !verge.config.enable_system_proxy.clone().unwrap_or(false) + }; - match verge.patch_config(VergeConfig { + let patch = VergeConfig { enable_system_proxy: Some(new_value), ..VergeConfig::default() - }) { - Ok(_) => verge.update_systray(app_handle).unwrap(), - Err(err) => log::error!("{err}"), - } + }; + + crate::log_if_err!(core.patch_verge(patch, app_handle)); } "tun_mode" => { let core = app_handle.state::(); - let mut verge = core.verge.lock(); - let old_value = verge.config.enable_tun_mode.clone().unwrap_or(false); - let new_value = !old_value; + let new_value = { + let verge = core.verge.lock(); + !verge.config.enable_tun_mode.clone().unwrap_or(false) + }; - match verge.patch_config(VergeConfig { + let patch = VergeConfig { enable_tun_mode: Some(new_value), ..VergeConfig::default() - }) { - Ok(_) => verge.update_systray(app_handle).unwrap(), - Err(err) => log::error!("{err}"), - } + }; + + crate::log_if_err!(core.patch_verge(patch, app_handle)); } "restart_clash" => { let core = app_handle.state::(); @@ -104,7 +104,7 @@ fn main() -> std::io::Result<()> { cmds::restart_sidecar, cmds::get_sys_proxy, cmds::get_cur_proxy, - cmds::kill_sidecars, + cmds::kill_sidecar, cmds::open_app_dir, cmds::open_logs_dir, // clash @@ -122,7 +122,6 @@ fn main() -> std::io::Result<()> { cmds::delete_profile, cmds::select_profile, cmds::get_profiles, - cmds::sync_profiles, cmds::enhance_profiles, cmds::change_profile_chain, cmds::change_profile_valid, diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 0613407..0b7dacb 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -1,4 +1,7 @@ +use anyhow::{bail, Result}; use nanoid::nanoid; +use std::path::PathBuf; +use std::process::Command; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; @@ -41,6 +44,38 @@ pub fn parse_str(target: &str, key: &str) -> Option { } } +/// open file +/// use vscode by default +pub fn open_file(path: PathBuf) -> Result<()> { + // use vscode first + if let Ok(code) = which::which("code") { + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + + if let Err(err) = Command::new(code) + .creation_flags(0x08000000) + .arg(path) + .spawn() + { + bail!(format!("failed to open file by VScode for `{err}`")); + } + } + + #[cfg(not(target_os = "windows"))] + if let Err(err) = Command::new(code).arg(path).spawn() { + bail!(format!("failed to open file by VScode for `{err}`")); + } + + return Ok(()); + } + + match open::that(path) { + Ok(_) => Ok(()), + Err(err) => bail!(format!("failed to open file for `{err}`")), + } +} + #[macro_export] macro_rules! log_if_err { ($result: expr) => { diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 0eb69c4..a7d6906 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -21,9 +21,9 @@ pub fn resolve_setup(app: &App) { /// reset system proxy pub fn resolve_reset(app_handle: &AppHandle) { let core = app_handle.state::(); - let mut verge = core.verge.lock(); + let mut sysopt = core.sysopt.lock(); - verge.reset_sysproxy(); + sysopt.reset_sysproxy(); } /// customize the window theme diff --git a/src/components/layout/update-dialog.tsx b/src/components/layout/update-dialog.tsx index 2d2c3f4..f35de41 100644 --- a/src/components/layout/update-dialog.tsx +++ b/src/components/layout/update-dialog.tsx @@ -13,7 +13,7 @@ import { } from "@mui/material"; import { relaunch } from "@tauri-apps/api/process"; import { checkUpdate, installUpdate } from "@tauri-apps/api/updater"; -import { killSidecars, restartSidecar } from "../../services/cmds"; +import { killSidecar, restartSidecar } from "../../services/cmds"; import { atomUpdateState } from "../../services/states"; import Notice from "../base/base-notice"; @@ -41,7 +41,7 @@ const UpdateDialog = (props: Props) => { setUpdateState(true); try { - await killSidecars(); + await killSidecar(); await installUpdate(); await relaunch(); } catch (err: any) { diff --git a/src/services/cmds.ts b/src/services/cmds.ts index bc4555e..4cc2559 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -6,10 +6,6 @@ export async function getProfiles() { return invoke("get_profiles"); } -export async function syncProfiles() { - return invoke("sync_profiles"); -} - export async function enhanceProfiles() { return invoke("enhance_profiles"); } @@ -94,8 +90,8 @@ export async function restartSidecar() { return invoke("restart_sidecar"); } -export async function killSidecars() { - return invoke("kill_sidecars"); +export async function killSidecar() { + return invoke("kill_sidecar"); } export async function openAppDir() {