diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index c94d217..a39bfc4 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,6 +1,6 @@ use crate::{ - core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, Verge}, - utils::{dirs, help, sysopt::SysProxyConfig}, + core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, Verge}, + utils::{dirs, help, sysopt::SysProxyConfig}, }; use crate::{log_if_err, ret_err, wrap_err}; use anyhow::Result; @@ -12,28 +12,28 @@ type CmdResult = Result; /// get all profiles from `profiles.yaml` #[tauri::command] pub fn get_profiles(core: State<'_, Core>) -> CmdResult { - let profiles = core.profiles.lock(); - Ok(profiles.clone()) + let profiles = core.profiles.lock(); + Ok(profiles.clone()) } /// manually exec enhanced profile #[tauri::command] pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult { - wrap_err!(core.activate_enhanced(false)) + wrap_err!(core.activate_enhanced(false)) } /// import the profile from url /// and save to `profiles.yaml` #[tauri::command] pub async fn import_profile( - url: String, - option: Option, - core: State<'_, Core>, + url: String, + option: Option, + core: State<'_, Core>, ) -> CmdResult { - let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; + let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; - let mut profiles = core.profiles.lock(); - wrap_err!(profiles.append_item(item)) + let mut profiles = core.profiles.lock(); + wrap_err!(profiles.append_item(item)) } /// new a profile @@ -41,137 +41,137 @@ pub async fn import_profile( /// view the temp profile file by using vscode or other editor #[tauri::command] pub async fn create_profile( - item: PrfItem, // partial - file_data: Option, - core: State<'_, Core>, + item: PrfItem, // partial + file_data: Option, + core: State<'_, Core>, ) -> CmdResult { - let item = wrap_err!(PrfItem::from(item, file_data).await)?; + let item = wrap_err!(PrfItem::from(item, file_data).await)?; - let mut profiles = core.profiles.lock(); - wrap_err!(profiles.append_item(item)) + let mut profiles = core.profiles.lock(); + wrap_err!(profiles.append_item(item)) } /// Update the profile #[tauri::command] pub async fn update_profile( - index: String, - option: Option, - core: State<'_, Core>, + index: String, + option: Option, + core: State<'_, Core>, ) -> CmdResult { - wrap_err!(Core::update_profile_item(core.inner().clone(), index, option).await) + wrap_err!(Core::update_profile_item(core.inner().clone(), index, option).await) } /// change the current profile #[tauri::command] pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock(); - wrap_err!(profiles.put_current(index))?; + let mut profiles = core.profiles.lock(); + wrap_err!(profiles.put_current(index))?; - drop(profiles); + drop(profiles); - wrap_err!(core.activate_enhanced(false)) + wrap_err!(core.activate_enhanced(false)) } /// change the profile chain #[tauri::command] pub fn change_profile_chain(chain: Option>, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock(); - profiles.put_chain(chain); + let mut profiles = core.profiles.lock(); + profiles.put_chain(chain); - drop(profiles); + drop(profiles); - wrap_err!(core.activate_enhanced(false)) + wrap_err!(core.activate_enhanced(false)) } /// change the profile valid fields #[tauri::command] pub fn change_profile_valid(valid: Option>, core: State) -> CmdResult { - let mut profiles = core.profiles.lock(); - profiles.put_valid(valid); + let mut profiles = core.profiles.lock(); + profiles.put_valid(valid); - drop(profiles); + drop(profiles); - wrap_err!(core.activate_enhanced(false)) + wrap_err!(core.activate_enhanced(false)) } /// delete profile item #[tauri::command] pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock(); + let mut profiles = core.profiles.lock(); - if wrap_err!(profiles.delete_item(index))? { - drop(profiles); + if wrap_err!(profiles.delete_item(index))? { + drop(profiles); - log_if_err!(core.activate_enhanced(false)); - } + log_if_err!(core.activate_enhanced(false)); + } - Ok(()) + Ok(()) } /// patch the profile config #[tauri::command] pub fn patch_profile(index: String, profile: PrfItem, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock(); - wrap_err!(profiles.patch_item(index, profile))?; - drop(profiles); + let mut profiles = core.profiles.lock(); + wrap_err!(profiles.patch_item(index, profile))?; + drop(profiles); - // update cron task - let mut timer = core.timer.lock(); - wrap_err!(timer.refresh()) + // update cron task + let mut timer = core.timer.lock(); + wrap_err!(timer.refresh()) } /// run vscode command to edit the profile #[tauri::command] pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult { - let profiles = core.profiles.lock(); - let item = wrap_err!(profiles.get_item(&index))?; + let profiles = core.profiles.lock(); + let item = wrap_err!(profiles.get_item(&index))?; - let file = item.file.clone(); - if file.is_none() { - ret_err!("the file is null"); - } + let file = item.file.clone(); + if file.is_none() { + ret_err!("the file is null"); + } - let path = dirs::app_profiles_dir().join(file.unwrap()); - if !path.exists() { - ret_err!("the file not found"); - } + let path = dirs::app_profiles_dir().join(file.unwrap()); + if !path.exists() { + ret_err!("the file not found"); + } - wrap_err!(help::open_file(path)) + wrap_err!(help::open_file(path)) } /// read the profile item file data #[tauri::command] pub fn read_profile_file(index: String, core: State<'_, Core>) -> CmdResult { - let profiles = core.profiles.lock(); + let profiles = core.profiles.lock(); - let item = wrap_err!(profiles.get_item(&index))?; - let data = wrap_err!(item.read_file())?; + let item = wrap_err!(profiles.get_item(&index))?; + let data = wrap_err!(item.read_file())?; - Ok(data) + Ok(data) } /// save the profile item file data #[tauri::command] pub fn save_profile_file( - index: String, - file_data: Option, - core: State<'_, Core>, + index: String, + file_data: Option, + core: State<'_, Core>, ) -> CmdResult { - if file_data.is_none() { - return Ok(()); - } + if file_data.is_none() { + return Ok(()); + } - let profiles = core.profiles.lock(); - let item = wrap_err!(profiles.get_item(&index))?; - wrap_err!(item.save_file(file_data.unwrap())) + let profiles = core.profiles.lock(); + let item = wrap_err!(profiles.get_item(&index))?; + wrap_err!(item.save_file(file_data.unwrap())) } /// get the clash core info from the state /// the caller can also get the infomation by clash's api #[tauri::command] pub fn get_clash_info(core: State<'_, Core>) -> CmdResult { - let clash = core.clash.lock(); - Ok(clash.info.clone()) + let clash = core.clash.lock(); + Ok(clash.info.clone()) } /// update the clash core config @@ -179,138 +179,138 @@ pub fn get_clash_info(core: State<'_, Core>) -> CmdResult { /// then we should save the latest config #[tauri::command] pub fn patch_clash_config( - payload: Mapping, - app_handle: tauri::AppHandle, - core: State<'_, Core>, + payload: Mapping, + app_handle: tauri::AppHandle, + core: State<'_, Core>, ) -> CmdResult { - wrap_err!(core.patch_clash(payload, &app_handle)) + wrap_err!(core.patch_clash(payload, &app_handle)) } /// get the verge config #[tauri::command] pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { - let verge = core.verge.lock(); - Ok(verge.clone()) + let verge = core.verge.lock(); + Ok(verge.clone()) } /// patch the verge config /// this command only save the config and not responsible for other things #[tauri::command] pub fn patch_verge_config( - payload: Verge, - app_handle: tauri::AppHandle, - core: State<'_, Core>, + payload: Verge, + app_handle: tauri::AppHandle, + core: State<'_, Core>, ) -> CmdResult { - wrap_err!(core.patch_verge(payload, &app_handle)) + wrap_err!(core.patch_verge(payload, &app_handle)) } /// change clash core #[tauri::command] pub fn change_clash_core(core: State<'_, Core>, clash_core: Option) -> CmdResult { - wrap_err!(core.change_core(clash_core)) + wrap_err!(core.change_core(clash_core)) } /// restart the sidecar #[tauri::command] pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult { - wrap_err!(core.restart_clash()) + wrap_err!(core.restart_clash()) } /// kill all sidecars when update app #[tauri::command] pub fn kill_sidecar() { - api::process::kill_children(); + api::process::kill_children(); } /// get the system proxy #[tauri::command] pub fn get_sys_proxy() -> Result { - wrap_err!(SysProxyConfig::get_sys()) + 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()) + let sysopt = core.sysopt.lock(); + wrap_err!(sysopt.get_sysproxy()) } /// open app config dir #[tauri::command] pub fn open_app_dir() -> Result<(), String> { - let app_dir = dirs::app_home_dir(); - wrap_err!(open::that(app_dir)) + let app_dir = dirs::app_home_dir(); + wrap_err!(open::that(app_dir)) } /// open logs dir #[tauri::command] pub fn open_logs_dir() -> Result<(), String> { - let log_dir = dirs::app_logs_dir(); - wrap_err!(open::that(log_dir)) + let log_dir = dirs::app_logs_dir(); + wrap_err!(open::that(log_dir)) } /// service mode #[cfg(windows)] pub mod service { - use super::*; - use crate::core::win_service::JsonResponse; + use super::*; + use crate::core::win_service::JsonResponse; - #[tauri::command] - pub async fn start_service() -> Result<(), String> { - wrap_err!(crate::core::Service::start_service().await) - } + #[tauri::command] + pub async fn start_service() -> Result<(), String> { + wrap_err!(crate::core::Service::start_service().await) + } - #[tauri::command] - pub async fn stop_service() -> Result<(), String> { - wrap_err!(crate::core::Service::stop_service().await) - } + #[tauri::command] + pub async fn stop_service() -> Result<(), String> { + wrap_err!(crate::core::Service::stop_service().await) + } - #[tauri::command] - pub async fn check_service() -> Result { - // no log - match crate::core::Service::check_service().await { - Ok(res) => Ok(res), - Err(err) => Err(err.to_string()), - } + #[tauri::command] + pub async fn check_service() -> Result { + // no log + match crate::core::Service::check_service().await { + Ok(res) => Ok(res), + Err(err) => Err(err.to_string()), } + } - #[tauri::command] - pub async fn install_service() -> Result<(), String> { - wrap_err!(crate::core::Service::install_service().await) - } + #[tauri::command] + pub async fn install_service() -> Result<(), String> { + wrap_err!(crate::core::Service::install_service().await) + } - #[tauri::command] - pub async fn uninstall_service() -> Result<(), String> { - wrap_err!(crate::core::Service::uninstall_service().await) - } + #[tauri::command] + pub async fn uninstall_service() -> Result<(), String> { + wrap_err!(crate::core::Service::uninstall_service().await) + } } #[cfg(not(windows))] pub mod service { - use super::*; + use super::*; - #[tauri::command] - pub async fn start_service() -> Result<(), String> { - Ok(()) - } + #[tauri::command] + pub async fn start_service() -> Result<(), String> { + Ok(()) + } - #[tauri::command] - pub async fn stop_service() -> Result<(), String> { - Ok(()) - } + #[tauri::command] + pub async fn stop_service() -> Result<(), String> { + Ok(()) + } - #[tauri::command] - pub async fn check_service() -> Result<(), String> { - Ok(()) - } + #[tauri::command] + pub async fn check_service() -> Result<(), String> { + Ok(()) + } - #[tauri::command] - pub async fn install_service() -> Result<(), String> { - Ok(()) - } - #[tauri::command] - pub async fn uninstall_service() -> Result<(), String> { - Ok(()) - } + #[tauri::command] + pub async fn install_service() -> Result<(), String> { + Ok(()) + } + #[tauri::command] + pub async fn uninstall_service() -> Result<(), String> { + Ok(()) + } } diff --git a/src-tauri/src/core/clash.rs b/src-tauri/src/core/clash.rs index cae30cc..d73c574 100644 --- a/src-tauri/src/core/clash.rs +++ b/src-tauri/src/core/clash.rs @@ -5,304 +5,304 @@ use serde_yaml::{Mapping, Value}; #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct ClashInfo { - /// clash sidecar status - pub status: String, + /// clash sidecar status + pub status: String, - /// clash core port - pub port: Option, + /// clash core port + pub port: Option, - /// same as `external-controller` - pub server: Option, + /// same as `external-controller` + pub server: Option, - /// clash secret - pub secret: Option, + /// clash secret + pub secret: Option, - /// mode - pub mode: Option, + /// mode + pub mode: Option, } impl ClashInfo { - /// parse the clash's config.yaml - /// get some information - pub fn from(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 key_mode = Value::from("mode"); + /// parse the clash's config.yaml + /// get some information + pub fn from(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 key_mode = Value::from("mode"); - let port = match 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 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 port = match 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 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, + }, + }; - // `external-controller` could be - // "127.0.0.1:9090" or ":9090" - let server = match config.get(&key_server) { - Some(value) => { - let val_str = value.as_str().unwrap_or(""); + // `external-controller` could be + // "127.0.0.1:9090" or ":9090" + let server = match config.get(&key_server) { + Some(value) => { + let val_str = value.as_str().unwrap_or(""); - if val_str.starts_with(":") { - Some(format!("127.0.0.1{val_str}")) - } else { - Some(val_str.into()) - } - } - _ => None, - }; - - let secret = match 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, - }; - - let mode = match config.get(&key_mode) { - Some(value) => match value { - Value::String(val_str) => Some(val_str.clone()), - _ => None, - }, - _ => None, - }; - - ClashInfo { - status: "init".into(), - port, - server, - secret, - mode, + if val_str.starts_with(":") { + Some(format!("127.0.0.1{val_str}")) + } else { + Some(val_str.into()) } + } + _ => None, + }; + + let secret = match 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, + }; + + let mode = match config.get(&key_mode) { + Some(value) => match value { + Value::String(val_str) => Some(val_str.clone()), + _ => None, + }, + _ => None, + }; + + ClashInfo { + status: "init".into(), + port, + server, + secret, + mode, } + } } pub struct Clash { - /// maintain the clash config - pub config: Mapping, + /// maintain the clash config + pub config: Mapping, - /// some info - pub info: ClashInfo, + /// some info + pub info: ClashInfo, } impl Clash { - pub fn new() -> Clash { - let config = Clash::read_config(); - let info = ClashInfo::from(&config); + pub fn new() -> Clash { + let config = Clash::read_config(); + let info = ClashInfo::from(&config); - Clash { config, info } + Clash { config, info } + } + + /// get clash config + pub fn read_config() -> Mapping { + config::read_yaml::(dirs::clash_path()) + } + + /// save the clash config + pub fn save_config(&self) -> Result<()> { + config::save_yaml( + dirs::clash_path(), + &self.config, + Some("# Default Config For Clash Core\n\n"), + ) + } + + /// patch update the clash config + /// if the port is changed then return true + pub fn patch_config(&mut self, patch: Mapping) -> Result<(bool, bool)> { + let port_key = Value::from("mixed-port"); + let server_key = Value::from("external-controller"); + let secret_key = Value::from("secret"); + let mode_key = Value::from("mode"); + + let mut change_port = false; + let mut change_info = false; + let mut change_mode = false; + + for (key, value) in patch.into_iter() { + if key == port_key { + change_port = true; + } + + if key == mode_key { + change_mode = true; + } + + if key == port_key || key == server_key || key == secret_key || key == mode_key { + change_info = true; + } + + self.config.insert(key, value); } - /// get clash config - pub fn read_config() -> Mapping { - config::read_yaml::(dirs::clash_path()) + if change_info { + self.info = ClashInfo::from(&self.config); } - /// save the clash config - pub fn save_config(&self) -> Result<()> { - config::save_yaml( - dirs::clash_path(), - &self.config, - Some("# Default Config For Clash Core\n\n"), - ) + self.save_config()?; + + Ok((change_port, change_mode)) + } + + /// revise the `tun` and `dns` config + pub 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)); + }; } - /// patch update the clash config - /// if the port is changed then return true - pub fn patch_config(&mut self, patch: Mapping) -> Result<(bool, bool)> { - let port_key = Value::from("mixed-port"); - let server_key = Value::from("external-controller"); - let secret_key = Value::from("secret"); - let mode_key = Value::from("mode"); - - let mut change_port = false; - let mut change_info = false; - let mut change_mode = false; - - for (key, value) in patch.into_iter() { - if key == port_key { - change_port = true; - } - - if key == mode_key { - change_mode = true; - } - - if key == port_key || key == server_key || key == secret_key || key == mode_key { - change_info = true; - } - - self.config.insert(key, value); + // 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)); } - - if change_info { - self.info = ClashInfo::from(&self.config); - } - - self.save_config()?; - - Ok((change_port, change_mode)) + }; } - /// revise the `tun` and `dns` config - pub 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)); - }; - } + // tun config + let tun_val = config.get(&Value::from("tun")); + let mut new_tun = Mapping::new(); - // 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); - - if enable { - // 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(); - } - revise!(new_dns, "enable", enable); - - // 借鉴cfw的默认配置 - 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 + if tun_val.is_some() && tun_val.as_ref().unwrap().is_mapping() { + new_tun = tun_val.as_ref().unwrap().as_mapping().unwrap().clone(); } - /// only 5 default fields available (clash config fields) - /// convert to lowercase - pub 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", - ]; + revise!(new_tun, "enable", enable); - 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 + 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); } - /// more clash config fields available - /// convert to lowercase - pub 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", - ]; + revise!(config, "tun", new_tun); - let mut new_config = Mapping::new(); + if enable { + // dns config + let dns_val = config.get(&Value::from("dns")); + let mut new_dns = 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(); + if dns_val.is_some() && dns_val.as_ref().unwrap().is_mapping() { + new_dns = dns_val.as_ref().unwrap().as_mapping().unwrap().clone(); + } + revise!(new_dns, "enable", enable); - // filter - if !not_allow.contains(&&*key_str) { - new_config.insert(Value::String(key_str), value); - } - }); - } + // 借鉴cfw的默认配置 + 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>); - new_config + #[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 + } + + /// only 5 default fields available (clash config fields) + /// convert to lowercase + pub 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 + pub 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() - } + fn default() -> Self { + Clash::new() + } } diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 843e608..b6cb1d8 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -35,461 +35,467 @@ static mut WINDOW_CLOSABLE: bool = true; #[derive(Clone)] pub struct Core { - pub clash: Arc>, + pub clash: Arc>, - pub verge: Arc>, + pub verge: Arc>, - pub profiles: Arc>, + pub profiles: Arc>, - pub service: Arc>, + pub service: Arc>, - pub sysopt: Arc>, + pub sysopt: Arc>, - pub timer: Arc>, + pub timer: Arc>, - pub window: Arc>>, + pub window: Arc>>, } impl Core { - pub fn new() -> Core { - let clash = Clash::new(); - let verge = Verge::new(); - let profiles = Profiles::new(); - let service = Service::new(); + pub fn new() -> Core { + let clash = Clash::new(); + let verge = Verge::new(); + let profiles = Profiles::new(); + let service = Service::new(); - Core { - clash: Arc::new(Mutex::new(clash)), - 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())), - timer: Arc::new(Mutex::new(Timer::new())), - window: Arc::new(Mutex::new(None)), - } + Core { + clash: Arc::new(Mutex::new(clash)), + 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())), + timer: Arc::new(Mutex::new(Timer::new())), + window: Arc::new(Mutex::new(None)), + } + } + + /// initialize the core state + pub fn init(&self, app_handle: tauri::AppHandle) { + let verge = self.verge.lock(); + let clash_core = verge.clash_core.clone(); + + let mut service = self.service.lock(); + service.set_core(clash_core); + + #[cfg(windows)] + { + let enable = verge.enable_service_mode.clone(); + service.set_mode(enable.unwrap_or(false)); } - /// initialize the core state - pub fn init(&self, app_handle: tauri::AppHandle) { - let verge = self.verge.lock(); - let clash_core = verge.clash_core.clone(); + log_if_err!(service.start()); + drop(verge); + drop(service); - let mut service = self.service.lock(); - service.set_core(clash_core); + log_if_err!(self.activate()); - #[cfg(windows)] - { - let enable = verge.enable_service_mode.clone(); - service.set_mode(enable.unwrap_or(false)); - } + let clash = self.clash.lock(); + let verge = self.verge.lock(); - log_if_err!(service.start()); - drop(verge); - drop(service); + let silent_start = verge.enable_silent_start.clone(); + let auto_launch = verge.enable_auto_launch.clone(); - log_if_err!(self.activate()); - - let clash = self.clash.lock(); - let verge = self.verge.lock(); - - let silent_start = verge.enable_silent_start.clone(); - let auto_launch = verge.enable_auto_launch.clone(); - - // silent start - if silent_start.unwrap_or(false) { - let window = self.window.lock(); - window.as_ref().map(|win| { - win.hide().unwrap(); - }); - } - - let mut sysopt = self.sysopt.lock(); - - 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 { - sleep(Duration::from_secs(2)).await; - log_if_err!(core.activate_enhanced(true)); - }); - - // timer initialize - let mut timer = self.timer.lock(); - timer.set_core(self.clone()); - log_if_err!(timer.refresh()); + // silent start + if silent_start.unwrap_or(false) { + let window = self.window.lock(); + window.as_ref().map(|win| { + win.hide().unwrap(); + }); } - /// save the window instance - pub fn set_win(&self, win: Option) { - let mut window = self.window.lock(); - *window = win; + let mut sysopt = self.sysopt.lock(); + + 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 { + sleep(Duration::from_secs(2)).await; + log_if_err!(core.activate_enhanced(true)); + }); + + // timer initialize + let mut timer = self.timer.lock(); + timer.set_core(self.clone()); + log_if_err!(timer.refresh()); + } + + /// save the window instance + pub fn set_win(&self, win: Option) { + let mut window = self.window.lock(); + *window = win; + } + + /// restart the clash sidecar + pub fn restart_clash(&self) -> Result<()> { + let mut service = self.service.lock(); + service.restart()?; + drop(service); + + self.activate()?; + self.activate_enhanced(true) + } + + /// change the clash core + pub fn change_core(&self, clash_core: Option) -> Result<()> { + let clash_core = clash_core.unwrap_or("clash".into()); + + if &clash_core != "clash" && &clash_core != "clash-meta" { + bail!("invalid clash core name \"{clash_core}\""); } - /// restart the clash sidecar - pub fn restart_clash(&self) -> Result<()> { - let mut service = self.service.lock(); - service.restart()?; - drop(service); + let mut verge = self.verge.lock(); + verge.patch_config(Verge { + clash_core: Some(clash_core.clone()), + ..Verge::default() + })?; + drop(verge); - self.activate()?; - self.activate_enhanced(true) + let mut service = self.service.lock(); + service.stop()?; + service.set_core(Some(clash_core)); + service.start()?; + drop(service); + + self.activate()?; + self.activate_enhanced(true) + } + + /// Patch Clash + /// handle the clash config changed + pub fn patch_clash(&self, patch: Mapping, app_handle: &AppHandle) -> Result<()> { + let ((changed_port, changed_mode), port) = { + let mut clash = self.clash.lock(); + (clash.patch_config(patch)?, clash.info.port.clone()) + }; + + // todo: port check + + if changed_port { + let mut service = self.service.lock(); + service.restart()?; + drop(service); + + self.activate()?; + self.activate_enhanced(true)?; + + let mut sysopt = self.sysopt.lock(); + let verge = self.verge.lock(); + sysopt.init_sysproxy(port, &verge); } - /// change the clash core - pub fn change_core(&self, clash_core: Option) -> Result<()> { - let clash_core = clash_core.unwrap_or("clash".into()); + if changed_mode { + self.update_systray(app_handle)?; + } - if &clash_core != "clash" && &clash_core != "clash-meta" { - bail!("invalid clash core name \"{clash_core}\""); - } + Ok(()) + } - let mut verge = self.verge.lock(); - verge.patch_config(Verge { - clash_core: Some(clash_core.clone()), - ..Verge::default() - })?; - drop(verge); + /// Patch Verge + pub fn patch_verge(&self, patch: Verge, 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(); + + #[cfg(windows)] + { + let service_mode = patch.enable_service_mode.clone(); + + if service_mode.is_some() { + let service_mode = service_mode.unwrap(); let mut service = self.service.lock(); service.stop()?; - service.set_core(Some(clash_core)); + service.set_mode(service_mode); service.start()?; drop(service); - self.activate()?; - self.activate_enhanced(true) + self.activate_enhanced(false)?; + } } - /// Patch Clash - /// handle the clash config changed - pub fn patch_clash(&self, patch: Mapping, app_handle: &AppHandle) -> Result<()> { - let ((changed_port, changed_mode), port) = { - let mut clash = self.clash.lock(); - (clash.patch_config(patch)?, clash.info.port.clone()) - }; - - // todo: port check - - if changed_port { - let mut service = self.service.lock(); - service.restart()?; - drop(service); - - self.activate()?; - self.activate_enhanced(true)?; - - let mut sysopt = self.sysopt.lock(); - let verge = self.verge.lock(); - sysopt.init_sysproxy(port, &verge); - } - - if changed_mode { - self.update_systray(app_handle)?; - } - - Ok(()) + if auto_launch.is_some() { + let mut sysopt = self.sysopt.lock(); + sysopt.update_launch(auto_launch)?; } - /// Patch Verge - pub fn patch_verge(&self, patch: Verge, 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(); - - #[cfg(windows)] - { - let service_mode = patch.enable_service_mode.clone(); - - if service_mode.is_some() { - let service_mode = service_mode.unwrap(); - - let mut service = self.service.lock(); - service.stop()?; - service.set_mode(service_mode); - service.start()?; - drop(service); - - self.activate_enhanced(false)?; - } - } - - 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(()) + 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(); } - /// update the system tray state - pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> { - let clash = self.clash.lock(); - let info = clash.info.clone(); - let mode = info.mode.as_ref(); - - let verge = self.verge.lock(); - let tray = app_handle.tray_handle(); - - let system_proxy = verge.enable_system_proxy.as_ref(); - let tun_mode = verge.enable_tun_mode.as_ref(); - - tray.get_item("rule_mode") - .set_selected((*mode.unwrap()).eq("rule"))?; - tray.get_item("global_mode") - .set_selected((*mode.unwrap()).eq("global"))?; - tray.get_item("direct_mode") - .set_selected((*mode.unwrap()).eq("direct"))?; - tray.get_item("script_mode") - .set_selected((*mode.unwrap()).eq("script"))?; - - 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(()) + if proxy_guard.unwrap_or(false) { + let sysopt = self.sysopt.lock(); + sysopt.guard_proxy(); } - /// activate the profile - /// auto activate enhanced profile - pub fn activate(&self) -> Result<()> { - let data = { - let profiles = self.profiles.lock(); - let data = profiles.gen_activate()?; - Clash::strict_filter(data) - }; - - let (mut config, info) = { - let clash = self.clash.lock(); - let config = clash.config.clone(); - let info = clash.info.clone(); - (config, info) - }; - - for (key, value) in data.into_iter() { - config.insert(key, value); - } - - let config = { - let verge = self.verge.lock(); - let tun_mode = verge.enable_tun_mode.unwrap_or(false); - Clash::_tun_mode(config, tun_mode) - }; - - let notice = { - let window = self.window.lock(); - Notice::from(window.clone()) - }; - - let service = self.service.lock(); - service.set_config(info, config, notice) + #[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`"); + } } - /// Enhanced - /// enhanced profiles mode - pub fn activate_enhanced(&self, skip: bool) -> Result<()> { - let window = self.window.lock(); - if window.is_none() { - bail!("failed to get the main window"); - } + // save the patch + let mut verge = self.verge.lock(); + verge.patch_config(patch)?; + drop(verge); - let event_name = help::get_uid("e"); - let event_name = format!("enhanced-cb-{event_name}"); - - // generate the payload - let payload = { - let profiles = self.profiles.lock(); - profiles.gen_enhanced(event_name.clone())? - }; - - // do not run enhanced - if payload.chain.len() == 0 { - if skip { - return Ok(()); - } - - drop(window); - return self.activate(); - } - - let tun_mode = { - let verge = self.verge.lock(); - verge.enable_tun_mode.unwrap_or(false) - }; - - let info = { - let clash = self.clash.lock(); - clash.info.clone() - }; - - let notice = Notice::from(window.clone()); - let service = self.service.clone(); - - let window = window.clone().unwrap(); - window.once(&event_name, move |event| { - let result = event.payload(); - - if result.is_none() { - log::warn!("event payload result is none"); - return; - } - - let result = result.unwrap(); - 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); - } - - let config = Clash::_tun_mode(config, tun_mode); - - let service = service.lock(); - log_if_err!(service.set_config(info, config, notice)); - - log::info!("profile enhanced status {}", result.status); - } - - result.error.map(|err| log::error!("{err}")); - }); - - let verge = self.verge.lock(); - let silent_start = verge.enable_silent_start.clone(); - - let closable = unsafe { WINDOW_CLOSABLE }; - - if silent_start.unwrap_or(false) && closable { - unsafe { - WINDOW_CLOSABLE = false; - } - - window.emit("script-handler-close", payload).unwrap(); - } else { - window.emit("script-handler", payload).unwrap(); - } - - Ok(()) + if system_proxy.is_some() || tun_mode.is_some() { + self.update_systray(app_handle)?; } - // update rule/global/direct/script mode - pub fn update_mode(&self, app_handle: &AppHandle, mode: &str) -> Result<()> { - let mut mapping = Mapping::new(); - mapping.insert(Value::from("mode"), Value::from(mode)); - - self.patch_clash(mapping, app_handle); - - let (config, info) = { - let clash = self.clash.lock(); - let config = clash.config.clone(); - let info = clash.info.clone(); - (config, info) - }; - - let notice = { - let window = self.window.lock(); - Notice::from(window.clone()) - }; - - let service = self.service.lock(); - service.patch_config(info, config, notice); - - Ok(()) + 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 clash = self.clash.lock(); + let info = clash.info.clone(); + let mode = info.mode.as_ref(); + + let verge = self.verge.lock(); + let tray = app_handle.tray_handle(); + + let system_proxy = verge.enable_system_proxy.as_ref(); + let tun_mode = verge.enable_tun_mode.as_ref(); + + tray + .get_item("rule_mode") + .set_selected((*mode.unwrap()).eq("rule"))?; + tray + .get_item("global_mode") + .set_selected((*mode.unwrap()).eq("global"))?; + tray + .get_item("direct_mode") + .set_selected((*mode.unwrap()).eq("direct"))?; + tray + .get_item("script_mode") + .set_selected((*mode.unwrap()).eq("script"))?; + + 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<()> { + let data = { + let profiles = self.profiles.lock(); + let data = profiles.gen_activate()?; + Clash::strict_filter(data) + }; + + let (mut config, info) = { + let clash = self.clash.lock(); + let config = clash.config.clone(); + let info = clash.info.clone(); + (config, info) + }; + + for (key, value) in data.into_iter() { + config.insert(key, value); + } + + let config = { + let verge = self.verge.lock(); + let tun_mode = verge.enable_tun_mode.unwrap_or(false); + Clash::_tun_mode(config, tun_mode) + }; + + let notice = { + let window = self.window.lock(); + Notice::from(window.clone()) + }; + + let service = self.service.lock(); + service.set_config(info, config, notice) + } + + /// Enhanced + /// enhanced profiles mode + pub fn activate_enhanced(&self, skip: bool) -> Result<()> { + let window = self.window.lock(); + if 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 = { + let profiles = self.profiles.lock(); + profiles.gen_enhanced(event_name.clone())? + }; + + // do not run enhanced + if payload.chain.len() == 0 { + if skip { + return Ok(()); + } + + drop(window); + return self.activate(); + } + + let tun_mode = { + let verge = self.verge.lock(); + verge.enable_tun_mode.unwrap_or(false) + }; + + let info = { + let clash = self.clash.lock(); + clash.info.clone() + }; + + let notice = Notice::from(window.clone()); + let service = self.service.clone(); + + let window = window.clone().unwrap(); + window.once(&event_name, move |event| { + let result = event.payload(); + + if result.is_none() { + log::warn!("event payload result is none"); + return; + } + + let result = result.unwrap(); + 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); + } + + let config = Clash::_tun_mode(config, tun_mode); + + let service = service.lock(); + log_if_err!(service.set_config(info, config, notice)); + + log::info!("profile enhanced status {}", result.status); + } + + result.error.map(|err| log::error!("{err}")); + }); + + let verge = self.verge.lock(); + let silent_start = verge.enable_silent_start.clone(); + + let closable = unsafe { WINDOW_CLOSABLE }; + + if silent_start.unwrap_or(false) && closable { + unsafe { + WINDOW_CLOSABLE = false; + } + + window.emit("script-handler-close", payload).unwrap(); + } else { + window.emit("script-handler", payload).unwrap(); + } + + Ok(()) + } + + // update rule/global/direct/script mode + pub fn update_mode(&self, app_handle: &AppHandle, mode: &str) -> Result<()> { + let mut mapping = Mapping::new(); + mapping.insert(Value::from("mode"), Value::from(mode)); + + self.patch_clash(mapping, app_handle); + + let (config, info) = { + let clash = self.clash.lock(); + let config = clash.config.clone(); + let info = clash.info.clone(); + (config, info) + }; + + let notice = { + let window = self.window.lock(); + Notice::from(window.clone()) + }; + + let service = self.service.lock(); + service.patch_config(info, config, notice); + + Ok(()) + } } impl Core { - /// Static function - /// update profile item - pub async fn update_profile_item( - core: Core, - uid: String, - option: Option, - ) -> Result<()> { - let (url, opt) = { - let profiles = core.profiles.lock(); - let item = profiles.get_item(&uid)?; + /// Static function + /// update profile item + pub async fn update_profile_item( + core: Core, + uid: String, + option: Option, + ) -> Result<()> { + let (url, opt) = { + let profiles = core.profiles.lock(); + let item = profiles.get_item(&uid)?; - if let Some(typ) = item.itype.as_ref() { - // maybe only valid for `local` profile - if *typ != "remote" { - // reactivate the config - if Some(uid) == profiles.get_current() { - drop(profiles); - return core.activate_enhanced(false); - } - - return Ok(()); - } - } - - if item.url.is_none() { - bail!("failed to get the profile item url"); - } - - (item.url.clone().unwrap(), item.option.clone()) - }; - - let merged_opt = PrfOption::merge(opt, option); - let item = PrfItem::from_url(&url, None, None, merged_opt).await?; - - let mut profiles = core.profiles.lock(); - profiles.update_item(uid.clone(), item)?; - - // reactivate the profile - if Some(uid) == profiles.get_current() { + if let Some(typ) = item.itype.as_ref() { + // maybe only valid for `local` profile + if *typ != "remote" { + // reactivate the config + if Some(uid) == profiles.get_current() { drop(profiles); - core.activate_enhanced(false)?; - } + return core.activate_enhanced(false); + } - Ok(()) + return Ok(()); + } + } + + if item.url.is_none() { + bail!("failed to get the profile item url"); + } + + (item.url.clone().unwrap(), item.option.clone()) + }; + + let merged_opt = PrfOption::merge(opt, option); + let item = PrfItem::from_url(&url, None, None, merged_opt).await?; + + let mut profiles = core.profiles.lock(); + profiles.update_item(uid.clone(), item)?; + + // reactivate the profile + if Some(uid) == profiles.get_current() { + drop(profiles); + core.activate_enhanced(false)?; } + + Ok(()) + } } diff --git a/src-tauri/src/core/notice.rs b/src-tauri/src/core/notice.rs index 4a3ec2d..8e942da 100644 --- a/src-tauri/src/core/notice.rs +++ b/src-tauri/src/core/notice.rs @@ -3,33 +3,33 @@ use tauri::Window; #[derive(Debug, Default, Clone)] pub struct Notice { - win: Option, + win: Option, } impl Notice { - pub fn from(win: Option) -> Notice { - Notice { win } - } + pub fn from(win: Option) -> Notice { + Notice { win } + } - pub fn set_win(&mut self, win: Option) { - self.win = win; - } + pub fn set_win(&mut self, win: Option) { + self.win = win; + } - pub fn refresh_clash(&self) { - if let Some(window) = self.win.as_ref() { - log_if_err!(window.emit("verge://refresh-clash-config", "yes")); - } + pub fn refresh_clash(&self) { + if let Some(window) = self.win.as_ref() { + log_if_err!(window.emit("verge://refresh-clash-config", "yes")); } + } - pub fn refresh_verge(&self) { - if let Some(window) = self.win.as_ref() { - log_if_err!(window.emit("verge://refresh-verge-config", "yes")); - } + pub fn refresh_verge(&self) { + if let Some(window) = self.win.as_ref() { + log_if_err!(window.emit("verge://refresh-verge-config", "yes")); } + } - pub fn refresh_profiles(&self) { - if let Some(window) = self.win.as_ref() { - log_if_err!(window.emit("verge://refresh-profiles-config", "yes")); - } + pub fn refresh_profiles(&self) { + if let Some(window) = self.win.as_ref() { + log_if_err!(window.emit("verge://refresh-profiles-config", "yes")); } + } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 311c379..8a9c1ba 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -14,462 +14,456 @@ static mut CLASH_CORE: &str = "clash"; #[derive(Debug)] pub struct Service { - sidecar: Option, + sidecar: Option, - #[allow(unused)] - service_mode: bool, + #[allow(unused)] + service_mode: bool, } impl Service { - pub fn new() -> Service { - Service { - sidecar: None, - service_mode: false, + pub fn new() -> Service { + Service { + sidecar: None, + service_mode: false, + } + } + + pub fn set_core(&mut self, clash_core: Option) { + unsafe { + CLASH_CORE = Box::leak(clash_core.unwrap_or("clash".into()).into_boxed_str()); + } + } + + #[allow(unused)] + pub fn set_mode(&mut self, enable: bool) { + self.service_mode = enable; + } + + #[cfg(not(windows))] + pub fn start(&mut self) -> Result<()> { + self.start_clash_by_sidecar() + } + + #[cfg(windows)] + pub fn start(&mut self) -> Result<()> { + if !self.service_mode { + return self.start_clash_by_sidecar(); + } + + tauri::async_runtime::spawn(async move { + match Self::check_service().await { + Ok(status) => { + // 未启动clash + if status.code != 0 { + if let Err(err) = Self::start_clash_by_service().await { + log::error!("{err}"); + } + } } + Err(err) => log::error!("{err}"), + } + }); + + Ok(()) + } + + #[cfg(not(windows))] + pub fn stop(&mut self) -> Result<()> { + self.stop_clash_by_sidecar() + } + + #[cfg(windows)] + pub fn stop(&mut self) -> Result<()> { + if !self.service_mode { + return self.stop_clash_by_sidecar(); } - pub fn set_core(&mut self, clash_core: Option) { - unsafe { - CLASH_CORE = Box::leak(clash_core.unwrap_or("clash".into()).into_boxed_str()); + tauri::async_runtime::spawn(async move { + if let Err(err) = Self::stop_clash_by_service().await { + log::error!("{err}"); + } + }); + + Ok(()) + } + + pub fn restart(&mut self) -> Result<()> { + self.stop()?; + self.start() + } + + /// start the clash sidecar + fn start_clash_by_sidecar(&mut self) -> Result<()> { + if self.sidecar.is_some() { + bail!("could not run clash sidecar twice"); + } + + let app_dir = dirs::app_home_dir(); + let app_dir = app_dir.as_os_str().to_str().unwrap(); + + let clash_core = unsafe { CLASH_CORE }; + let cmd = Command::new_sidecar(clash_core)?; + 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), + _ => {} } + } + }); + + Ok(()) + } + + /// stop the clash sidecar + fn stop_clash_by_sidecar(&mut self) -> Result<()> { + if let Some(sidecar) = self.sidecar.take() { + sidecar.kill()?; + } + Ok(()) + } + + /// update clash config + /// using PUT methods + pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> { + if !self.service_mode && self.sidecar.is_none() { + bail!("did not start sidecar"); } - #[allow(unused)] - pub fn set_mode(&mut self, enable: bool) { - self.service_mode = enable; + let temp_path = dirs::profiles_temp_path(); + config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?; + + if info.server.is_none() { + if info.port.is_none() { + bail!("failed to parse config.yaml file"); + } else { + bail!("failed to parse the server"); + } } - #[cfg(not(windows))] - pub fn start(&mut self) -> Result<()> { - self.start_clash_by_sidecar() + 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); } - #[cfg(windows)] - pub fn start(&mut self) -> Result<()> { - if !self.service_mode { - return self.start_clash_by_sidecar(); - } + tauri::async_runtime::spawn(async move { + let mut data = HashMap::new(); + data.insert("path", temp_path.as_os_str().to_str().unwrap()); - tauri::async_runtime::spawn(async move { - match Self::check_service().await { - Ok(status) => { - // 未启动clash - if status.code != 0 { - if let Err(err) = Self::start_clash_by_service().await { - log::error!("{err}"); - } - } + // 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 with status \"{}\"", resp.status()); } - Err(err) => log::error!("{err}"), + + notice.refresh_clash(); + + // do not retry + break; + } + Err(err) => log::error!("failed to activate for `{err}`"), } - }); - - Ok(()) - } - - #[cfg(not(windows))] - pub fn stop(&mut self) -> Result<()> { - self.stop_clash_by_sidecar() - } - - #[cfg(windows)] - pub fn stop(&mut self) -> Result<()> { - if !self.service_mode { - return self.stop_clash_by_sidecar(); + } + Err(err) => log::error!("failed to activate for `{err}`"), } + sleep(Duration::from_millis(500)).await; + } + }); - tauri::async_runtime::spawn(async move { - if let Err(err) = Self::stop_clash_by_service().await { - log::error!("{err}"); - } - }); + Ok(()) + } - Ok(()) + /// patch clash config + pub fn patch_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> { + if !self.service_mode && self.sidecar.is_none() { + bail!("did not start sidecar"); } - pub fn restart(&mut self) -> Result<()> { - self.stop()?; - self.start() + if info.server.is_none() { + if info.port.is_none() { + bail!("failed to parse config.yaml file"); + } else { + bail!("failed to parse the server"); + } } - /// start the clash sidecar - fn start_clash_by_sidecar(&mut self) -> Result<()> { - if self.sidecar.is_some() { - bail!("could not run clash sidecar twice"); - } + let server = info.server.unwrap(); + let server = format!("http://{server}/configs"); - let app_dir = dirs::app_home_dir(); - let app_dir = app_dir.as_os_str().to_str().unwrap(); + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", "application/json".parse().unwrap()); - let clash_core = unsafe { CLASH_CORE }; - let cmd = Command::new_sidecar(clash_core)?; - let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?; + if let Some(secret) = info.secret.as_ref() { + let secret = format!("Bearer {}", secret.clone()).parse().unwrap(); + headers.insert("Authorization", secret); + } - self.sidecar = Some(cmd_child); + tauri::async_runtime::spawn(async move { + // retry 5 times + for _ in 0..5 { + match reqwest::ClientBuilder::new().no_proxy().build() { + Ok(client) => { + let builder = client.patch(&server).headers(headers.clone()).json(&config); - // 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), - _ => {} + match builder.send().await { + Ok(resp) => { + if resp.status() != 204 { + log::error!("failed to activate clash with status \"{}\"", resp.status()); } + + notice.refresh_clash(); + + // do not retry + break; + } + Err(err) => log::error!("failed to activate for `{err}`"), } - }); - - Ok(()) - } - - /// stop the clash sidecar - fn stop_clash_by_sidecar(&mut self) -> Result<()> { - if let Some(sidecar) = self.sidecar.take() { - sidecar.kill()?; + } + Err(err) => log::error!("failed to activate for `{err}`"), } - Ok(()) - } + sleep(Duration::from_millis(500)).await; + } + }); - /// update clash config - /// using PUT methods - pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> { - if !self.service_mode && self.sidecar.is_none() { - bail!("did not start sidecar"); - } - - let temp_path = dirs::profiles_temp_path(); - config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?; - - if info.server.is_none() { - if info.port.is_none() { - bail!("failed to parse config.yaml file"); - } else { - bail!("failed to parse the server"); - } - } - - 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); - } - - tauri::async_runtime::spawn(async move { - 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 with status \"{}\"", - resp.status() - ); - } - - notice.refresh_clash(); - - // 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(()) - } - - /// patch clash config - pub fn patch_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> { - if !self.service_mode && self.sidecar.is_none() { - bail!("did not start sidecar"); - } - - if info.server.is_none() { - if info.port.is_none() { - bail!("failed to parse config.yaml file"); - } else { - bail!("failed to parse the server"); - } - } - - 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); - } - - tauri::async_runtime::spawn(async move { - // retry 5 times - for _ in 0..5 { - match reqwest::ClientBuilder::new().no_proxy().build() { - Ok(client) => { - let builder = client.patch(&server).headers(headers.clone()).json(&config); - - match builder.send().await { - Ok(resp) => { - if resp.status() != 204 { - log::error!( - "failed to activate clash with status \"{}\"", - resp.status() - ); - } - - notice.refresh_clash(); - - // 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(()) - } + Ok(()) + } } impl Drop for Service { - fn drop(&mut self) { - log_if_err!(self.stop()); - } + fn drop(&mut self) { + log_if_err!(self.stop()); + } } /// ### Service Mode /// #[cfg(windows)] pub mod win_service { - use super::*; - use anyhow::Context; - use deelevate::{PrivilegeLevel, Token}; - use runas::Command as RunasCommand; - use std::os::windows::process::CommandExt; - use std::{env::current_exe, process::Command as StdCommand}; + use super::*; + use anyhow::Context; + use deelevate::{PrivilegeLevel, Token}; + use runas::Command as RunasCommand; + use std::os::windows::process::CommandExt; + use std::{env::current_exe, process::Command as StdCommand}; - const SERVICE_NAME: &str = "clash_verge_service"; + const SERVICE_NAME: &str = "clash_verge_service"; - const SERVICE_URL: &str = "http://127.0.0.1:33211"; + const SERVICE_URL: &str = "http://127.0.0.1:33211"; - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct ResponseBody { - pub bin_path: String, - pub config_dir: String, - pub log_file: String, + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct ResponseBody { + pub bin_path: String, + pub config_dir: String, + pub log_file: String, + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct JsonResponse { + pub code: u64, + pub msg: String, + pub data: Option, + } + + impl Service { + /// Install the Clash Verge Service + /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 + pub async fn install_service() -> Result<()> { + let binary_path = dirs::service_path(); + let install_path = binary_path.with_file_name("install-service.exe"); + + if !install_path.exists() { + bail!("installer exe not found"); + } + + let token = Token::with_current_process()?; + let level = token.privilege_level()?; + + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?, + _ => StdCommand::new(install_path) + .creation_flags(0x08000000) + .status()?, + }; + + if !status.success() { + bail!( + "failed to install service with status {}", + status.code().unwrap() + ); + } + + Ok(()) } - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct JsonResponse { - pub code: u64, - pub msg: String, - pub data: Option, + /// Uninstall the Clash Verge Service + /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 + pub async fn uninstall_service() -> Result<()> { + let binary_path = dirs::service_path(); + let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); + + if !uninstall_path.exists() { + bail!("uninstaller exe not found"); + } + + let token = Token::with_current_process()?; + let level = token.privilege_level()?; + + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?, + _ => StdCommand::new(uninstall_path) + .creation_flags(0x08000000) + .status()?, + }; + + if !status.success() { + bail!( + "failed to uninstall service with status {}", + status.code().unwrap() + ); + } + + Ok(()) } - impl Service { - /// Install the Clash Verge Service - /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 - pub async fn install_service() -> Result<()> { - let binary_path = dirs::service_path(); - let install_path = binary_path.with_file_name("install-service.exe"); + /// [deprecated] + /// start service + /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 + pub async fn start_service() -> Result<()> { + let token = Token::with_current_process()?; + let level = token.privilege_level()?; - if !install_path.exists() { - bail!("installer exe not found"); - } + let args = ["start", SERVICE_NAME]; - let token = Token::with_current_process()?; - let level = token.privilege_level()?; + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?, + _ => StdCommand::new("sc").args(&args).status()?, + }; - let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?, - _ => StdCommand::new(install_path) - .creation_flags(0x08000000) - .status()?, - }; - - if !status.success() { - bail!( - "failed to install service with status {}", - status.code().unwrap() - ); - } - - Ok(()) - } - - /// Uninstall the Clash Verge Service - /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 - pub async fn uninstall_service() -> Result<()> { - let binary_path = dirs::service_path(); - let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); - - if !uninstall_path.exists() { - bail!("uninstaller exe not found"); - } - - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?, - _ => StdCommand::new(uninstall_path) - .creation_flags(0x08000000) - .status()?, - }; - - if !status.success() { - bail!( - "failed to uninstall service with status {}", - status.code().unwrap() - ); - } - - Ok(()) - } - - /// [deprecated] - /// start service - /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 - pub async fn start_service() -> Result<()> { - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - let args = ["start", SERVICE_NAME]; - - let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?, - _ => StdCommand::new("sc").args(&args).status()?, - }; - - match status.success() { - true => Ok(()), - false => bail!( - "failed to start service with status {}", - status.code().unwrap() - ), - } - } - - /// stop service - pub async fn stop_service() -> Result<()> { - let url = format!("{SERVICE_URL}/stop_service"); - let res = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .post(url) - .send() - .await? - .json::() - .await - .context("failed to connect to the Clash Verge Service")?; - - if res.code != 0 { - bail!(res.msg); - } - - Ok(()) - } - - /// check the windows service status - pub async fn check_service() -> Result { - let url = format!("{SERVICE_URL}/get_clash"); - let response = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .get(url) - .send() - .await? - .json::() - .await - .context("failed to connect to the Clash Verge Service")?; - - Ok(response) - } - - /// start the clash by service - pub(super) async fn start_clash_by_service() -> Result<()> { - let status = Self::check_service().await?; - - if status.code == 0 { - Self::stop_clash_by_service().await?; - sleep(Duration::from_secs(1)).await; - } - - let clash_core = unsafe { CLASH_CORE }; - let clash_bin = format!("{clash_core}.exe"); - let bin_path = current_exe().unwrap().with_file_name(clash_bin); - let bin_path = bin_path.as_os_str().to_str().unwrap(); - - let config_dir = dirs::app_home_dir(); - let config_dir = config_dir.as_os_str().to_str().unwrap(); - - let log_path = dirs::service_log_file(); - let log_path = log_path.as_os_str().to_str().unwrap(); - - let mut map = HashMap::new(); - map.insert("bin_path", bin_path); - map.insert("config_dir", config_dir); - map.insert("log_file", log_path); - - let url = format!("{SERVICE_URL}/start_clash"); - let res = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .post(url) - .json(&map) - .send() - .await? - .json::() - .await - .context("failed to connect to the Clash Verge Service")?; - - if res.code != 0 { - bail!(res.msg); - } - - Ok(()) - } - - /// stop the clash by service - pub(super) async fn stop_clash_by_service() -> Result<()> { - let url = format!("{SERVICE_URL}/stop_clash"); - let res = reqwest::ClientBuilder::new() - .no_proxy() - .build()? - .post(url) - .send() - .await? - .json::() - .await - .context("failed to connect to the Clash Verge Service")?; - - if res.code != 0 { - bail!(res.msg); - } - - Ok(()) - } + match status.success() { + true => Ok(()), + false => bail!( + "failed to start service with status {}", + status.code().unwrap() + ), + } } + + /// stop service + pub async fn stop_service() -> Result<()> { + let url = format!("{SERVICE_URL}/stop_service"); + let res = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .post(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } + + /// check the windows service status + pub async fn check_service() -> Result { + let url = format!("{SERVICE_URL}/get_clash"); + let response = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .get(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + Ok(response) + } + + /// start the clash by service + pub(super) async fn start_clash_by_service() -> Result<()> { + let status = Self::check_service().await?; + + if status.code == 0 { + Self::stop_clash_by_service().await?; + sleep(Duration::from_secs(1)).await; + } + + let clash_core = unsafe { CLASH_CORE }; + let clash_bin = format!("{clash_core}.exe"); + let bin_path = current_exe().unwrap().with_file_name(clash_bin); + let bin_path = bin_path.as_os_str().to_str().unwrap(); + + let config_dir = dirs::app_home_dir(); + let config_dir = config_dir.as_os_str().to_str().unwrap(); + + let log_path = dirs::service_log_file(); + let log_path = log_path.as_os_str().to_str().unwrap(); + + let mut map = HashMap::new(); + map.insert("bin_path", bin_path); + map.insert("config_dir", config_dir); + map.insert("log_file", log_path); + + let url = format!("{SERVICE_URL}/start_clash"); + let res = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .post(url) + .json(&map) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } + + /// stop the clash by service + pub(super) async fn stop_clash_by_service() -> Result<()> { + let url = format!("{SERVICE_URL}/stop_clash"); + let res = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .post(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } + } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 993e44d..d40fffe 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,6 +1,6 @@ #![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" )] mod cmds; @@ -8,177 +8,177 @@ mod core; mod utils; use crate::{ - core::Verge, - utils::{resolve, server}, + core::Verge, + utils::{resolve, server}, }; use serde_yaml::{Mapping, Value}; use tauri::{ - api, CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, + api, CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, }; fn main() -> std::io::Result<()> { - if server::check_singleton().is_err() { - println!("app exists"); - return Ok(()); - } + if server::check_singleton().is_err() { + println!("app exists"); + return Ok(()); + } - #[cfg(target_os = "windows")] - unsafe { - use crate::utils::dirs; + #[cfg(target_os = "windows")] + unsafe { + use crate::utils::dirs; - dirs::init_portable_flag(); - } + dirs::init_portable_flag(); + } - let tray_menu = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("open_window", "Show")) - .add_item(CustomMenuItem::new("rule_mode", "Rule Mode")) - .add_item(CustomMenuItem::new("global_mode", "Global Mode")) - .add_item(CustomMenuItem::new("direct_mode", "Direct Mode")) - .add_item(CustomMenuItem::new("script_mode", "Script Mode")) - .add_item(CustomMenuItem::new("system_proxy", "System Proxy")) - .add_item(CustomMenuItem::new("tun_mode", "Tun Mode")) - .add_item(CustomMenuItem::new("restart_clash", "Restart Clash")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q")); + let tray_menu = SystemTrayMenu::new() + .add_item(CustomMenuItem::new("open_window", "Show")) + .add_item(CustomMenuItem::new("rule_mode", "Rule Mode")) + .add_item(CustomMenuItem::new("global_mode", "Global Mode")) + .add_item(CustomMenuItem::new("direct_mode", "Direct Mode")) + .add_item(CustomMenuItem::new("script_mode", "Script Mode")) + .add_item(CustomMenuItem::new("system_proxy", "System Proxy")) + .add_item(CustomMenuItem::new("tun_mode", "Tun Mode")) + .add_item(CustomMenuItem::new("restart_clash", "Restart Clash")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q")); - #[allow(unused_mut)] - let mut builder = tauri::Builder::default() - .setup(|app| Ok(resolve::resolve_setup(app))) - .system_tray(SystemTray::new().with_menu(tray_menu)) - .on_system_tray_event(move |app_handle, event| match event { - SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { - "open_window" => { - resolve::create_window(app_handle); - } - "rule_mode" => { - let core = app_handle.state::(); - crate::log_if_err!(core.update_mode(app_handle, "rule")); - } - "global_mode" => { - let core = app_handle.state::(); - crate::log_if_err!(core.update_mode(app_handle, "global")); - } - "direct_mode" => { - let core = app_handle.state::(); - crate::log_if_err!(core.update_mode(app_handle, "direct")); - } - "script_mode" => { - let core = app_handle.state::(); - crate::log_if_err!(core.update_mode(app_handle, "script")); - } - "system_proxy" => { - let core = app_handle.state::(); + #[allow(unused_mut)] + let mut builder = tauri::Builder::default() + .setup(|app| Ok(resolve::resolve_setup(app))) + .system_tray(SystemTray::new().with_menu(tray_menu)) + .on_system_tray_event(move |app_handle, event| match event { + SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { + "open_window" => { + resolve::create_window(app_handle); + } + "rule_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "rule")); + } + "global_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "global")); + } + "direct_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "direct")); + } + "script_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "script")); + } + "system_proxy" => { + let core = app_handle.state::(); - let new_value = { - let verge = core.verge.lock(); - !verge.enable_system_proxy.clone().unwrap_or(false) - }; + let new_value = { + let verge = core.verge.lock(); + !verge.enable_system_proxy.clone().unwrap_or(false) + }; - let patch = Verge { - enable_system_proxy: Some(new_value), - ..Verge::default() - }; + let patch = Verge { + enable_system_proxy: Some(new_value), + ..Verge::default() + }; - crate::log_if_err!(core.patch_verge(patch, app_handle)); - } - "tun_mode" => { - let core = app_handle.state::(); + crate::log_if_err!(core.patch_verge(patch, app_handle)); + } + "tun_mode" => { + let core = app_handle.state::(); - let new_value = { - let verge = core.verge.lock(); - !verge.enable_tun_mode.clone().unwrap_or(false) - }; + let new_value = { + let verge = core.verge.lock(); + !verge.enable_tun_mode.clone().unwrap_or(false) + }; - let patch = Verge { - enable_tun_mode: Some(new_value), - ..Verge::default() - }; + let patch = Verge { + enable_tun_mode: Some(new_value), + ..Verge::default() + }; - crate::log_if_err!(core.patch_verge(patch, app_handle)); - } - "restart_clash" => { - let core = app_handle.state::(); - crate::log_if_err!(core.restart_clash()); - } - "quit" => { - resolve::resolve_reset(app_handle); - app_handle.exit(0); - } - _ => {} - }, - #[cfg(target_os = "windows")] - SystemTrayEvent::LeftClick { .. } => { - resolve::create_window(app_handle); - } - _ => {} - }) - .invoke_handler(tauri::generate_handler![ - // common - cmds::get_sys_proxy, - cmds::get_cur_proxy, - cmds::open_app_dir, - cmds::open_logs_dir, - cmds::kill_sidecar, - cmds::restart_sidecar, - // clash - cmds::get_clash_info, - cmds::patch_clash_config, - cmds::change_clash_core, - // verge - cmds::get_verge_config, - cmds::patch_verge_config, - // profile - cmds::view_profile, - cmds::patch_profile, - cmds::create_profile, - cmds::import_profile, - cmds::update_profile, - cmds::delete_profile, - cmds::select_profile, - cmds::get_profiles, - cmds::enhance_profiles, - cmds::change_profile_chain, - cmds::change_profile_valid, - cmds::read_profile_file, - cmds::save_profile_file, - // service mode - cmds::service::start_service, - cmds::service::stop_service, - cmds::service::check_service, - cmds::service::install_service, - cmds::service::uninstall_service, - ]); + crate::log_if_err!(core.patch_verge(patch, app_handle)); + } + "restart_clash" => { + let core = app_handle.state::(); + crate::log_if_err!(core.restart_clash()); + } + "quit" => { + resolve::resolve_reset(app_handle); + app_handle.exit(0); + } + _ => {} + }, + #[cfg(target_os = "windows")] + SystemTrayEvent::LeftClick { .. } => { + resolve::create_window(app_handle); + } + _ => {} + }) + .invoke_handler(tauri::generate_handler![ + // common + cmds::get_sys_proxy, + cmds::get_cur_proxy, + cmds::open_app_dir, + cmds::open_logs_dir, + cmds::kill_sidecar, + cmds::restart_sidecar, + // clash + cmds::get_clash_info, + cmds::patch_clash_config, + cmds::change_clash_core, + // verge + cmds::get_verge_config, + cmds::patch_verge_config, + // profile + cmds::view_profile, + cmds::patch_profile, + cmds::create_profile, + cmds::import_profile, + cmds::update_profile, + cmds::delete_profile, + cmds::select_profile, + cmds::get_profiles, + cmds::enhance_profiles, + cmds::change_profile_chain, + cmds::change_profile_valid, + cmds::read_profile_file, + cmds::save_profile_file, + // service mode + cmds::service::start_service, + cmds::service::stop_service, + cmds::service::check_service, + cmds::service::install_service, + cmds::service::uninstall_service, + ]); - #[cfg(target_os = "macos")] - { - use tauri::{Menu, MenuItem, Submenu}; + #[cfg(target_os = "macos")] + { + use tauri::{Menu, MenuItem, Submenu}; - let submenu_file = Submenu::new( - "File", - Menu::new() - .add_native_item(MenuItem::Undo) - .add_native_item(MenuItem::Redo) - .add_native_item(MenuItem::Copy) - .add_native_item(MenuItem::Paste) - .add_native_item(MenuItem::Cut) - .add_native_item(MenuItem::SelectAll), - ); - builder = builder.menu(Menu::new().add_submenu(submenu_file)); - } + let submenu_file = Submenu::new( + "File", + Menu::new() + .add_native_item(MenuItem::Undo) + .add_native_item(MenuItem::Redo) + .add_native_item(MenuItem::Copy) + .add_native_item(MenuItem::Paste) + .add_native_item(MenuItem::Cut) + .add_native_item(MenuItem::SelectAll), + ); + builder = builder.menu(Menu::new().add_submenu(submenu_file)); + } - builder - .build(tauri::generate_context!()) - .expect("error while running tauri application") - .run(|app_handle, e| match e { - tauri::RunEvent::ExitRequested { api, .. } => { - api.prevent_exit(); - } - tauri::RunEvent::Exit => { - resolve::resolve_reset(app_handle); - api::process::kill_children(); - } - _ => {} - }); + builder + .build(tauri::generate_context!()) + .expect("error while running tauri application") + .run(|app_handle, e| match e { + tauri::RunEvent::ExitRequested { api, .. } => { + api.prevent_exit(); + } + tauri::RunEvent::Exit => { + resolve::resolve_reset(app_handle); + api::process::kill_children(); + } + _ => {} + }); - Ok(()) + Ok(()) }