From 4668be6e24dbc36fc07074f2854ce5953fbc6b11 Mon Sep 17 00:00:00 2001 From: GyDi Date: Sat, 12 Nov 2022 11:37:23 +0800 Subject: [PATCH] chore: format rust code --- .editorconfig | 6 + src-tauri/build.rs | 2 +- src-tauri/rustfmt.toml | 2 +- src-tauri/src/cmds.rs | 342 ++++++------- src-tauri/src/config/field.rs | 170 +++---- src-tauri/src/config/merge.rs | 92 ++-- src-tauri/src/config/mod.rs | 82 +-- src-tauri/src/config/script.rs | 90 ++-- src-tauri/src/config/tun.rs | 116 ++--- src-tauri/src/core/handle.rs | 87 ++-- src-tauri/src/core/hotkey.rs | 262 +++++----- src-tauri/src/core/mod.rs | 572 ++++++++++----------- src-tauri/src/core/service.rs | 886 +++++++++++++++++---------------- src-tauri/src/core/sysopt.rs | 494 +++++++++--------- src-tauri/src/core/timer.rs | 260 +++++----- src-tauri/src/core/tray.rs | 228 ++++----- src-tauri/src/data/clash.rs | 266 +++++----- src-tauri/src/data/mod.rs | 22 +- src-tauri/src/data/prfitem.rs | 656 ++++++++++++------------ src-tauri/src/data/profiles.rs | 543 ++++++++++---------- src-tauri/src/data/verge.rs | 190 +++---- src-tauri/src/feat.rs | 114 ++--- src-tauri/src/main.rs | 246 ++++----- src-tauri/src/utils/config.rs | 72 +-- src-tauri/src/utils/dirs.rs | 110 ++-- src-tauri/src/utils/help.rs | 114 ++--- src-tauri/src/utils/init.rs | 142 +++--- src-tauri/src/utils/resolve.rs | 170 +++---- src-tauri/src/utils/server.rs | 40 +- src-tauri/src/utils/winhelp.rs | 138 ++--- src-tauri/tauri.conf.json | 16 +- 31 files changed, 3268 insertions(+), 3262 deletions(-) diff --git a/.editorconfig b/.editorconfig index 33fc5f0..8e94654 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,3 +5,9 @@ charset = utf-8 end_of_line = lf indent_size = 2 insert_final_newline = true + +[*.rs] +charset = utf-8 +end_of_line = lf +indent_size = 4 +insert_final_newline = true diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/rustfmt.toml b/src-tauri/rustfmt.toml index 550a098..11eda88 100644 --- a/src-tauri/rustfmt.toml +++ b/src-tauri/rustfmt.toml @@ -1,6 +1,6 @@ max_width = 100 hard_tabs = false -tab_spaces = 2 +tab_spaces = 4 newline_style = "Auto" use_small_heuristics = "Default" reorder_imports = true diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 27892d5..e8ac49f 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,7 +1,7 @@ use crate::{ - core::Core, - data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge}, - utils::{dirs, help}, + core::Core, + data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge}, + utils::{dirs, help}, }; use crate::{log_if_err, ret_err, wrap_err}; use anyhow::Result; @@ -14,27 +14,27 @@ type CmdResult = Result; /// get all profiles from `profiles.yaml` #[tauri::command] pub fn get_profiles() -> CmdResult { - let global = Data::global(); - let profiles = global.profiles.lock(); - Ok(profiles.clone()) + let global = Data::global(); + let profiles = global.profiles.lock(); + Ok(profiles.clone()) } /// manually exec enhanced profile #[tauri::command] pub fn enhance_profiles() -> CmdResult { - let core = Core::global(); - wrap_err!(core.activate()) + let core = Core::global(); + wrap_err!(core.activate()) } /// import the profile from url /// and save to `profiles.yaml` #[tauri::command] pub async fn import_profile(url: String, option: Option) -> 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 global = Data::global(); - let mut profiles = global.profiles.lock(); - wrap_err!(profiles.append_item(item)) + let global = Data::global(); + let mut profiles = global.profiles.lock(); + wrap_err!(profiles.append_item(item)) } /// new a profile @@ -42,166 +42,166 @@ pub async fn import_profile(url: String, option: Option) -> CmdResult /// view the temp profile file by using vscode or other editor #[tauri::command] pub async fn create_profile(item: PrfItem, file_data: Option) -> CmdResult { - let item = wrap_err!(PrfItem::from(item, file_data).await)?; + let item = wrap_err!(PrfItem::from(item, file_data).await)?; - let global = Data::global(); - let mut profiles = global.profiles.lock(); - wrap_err!(profiles.append_item(item)) + let global = Data::global(); + let mut profiles = global.profiles.lock(); + wrap_err!(profiles.append_item(item)) } /// Update the profile #[tauri::command] pub async fn update_profile(index: String, option: Option) -> CmdResult { - let core = Core::global(); - wrap_err!(core.update_profile_item(index, option).await) + let core = Core::global(); + wrap_err!(core.update_profile_item(index, option).await) } /// change the current profile #[tauri::command] pub fn select_profile(index: String) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); - wrap_err!(profiles.put_current(index))?; - drop(profiles); + let global = Data::global(); + let mut profiles = global.profiles.lock(); + wrap_err!(profiles.put_current(index))?; + drop(profiles); - let core = Core::global(); - wrap_err!(core.activate()) + let core = Core::global(); + wrap_err!(core.activate()) } /// change the profile chain #[tauri::command] pub fn change_profile_chain(chain: Option>) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); - wrap_err!(profiles.put_chain(chain))?; - drop(profiles); + let global = Data::global(); + let mut profiles = global.profiles.lock(); + wrap_err!(profiles.put_chain(chain))?; + drop(profiles); - let core = Core::global(); - wrap_err!(core.activate()) + let core = Core::global(); + wrap_err!(core.activate()) } /// change the profile valid fields #[tauri::command] pub fn change_profile_valid(valid: Option>) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); - wrap_err!(profiles.put_valid(valid))?; - drop(profiles); + let global = Data::global(); + let mut profiles = global.profiles.lock(); + wrap_err!(profiles.put_valid(valid))?; + drop(profiles); - let core = Core::global(); - wrap_err!(core.activate()) + let core = Core::global(); + wrap_err!(core.activate()) } /// delete profile item #[tauri::command] pub fn delete_profile(index: String) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); - if wrap_err!(profiles.delete_item(index))? { - drop(profiles); + let global = Data::global(); + let mut profiles = global.profiles.lock(); + if wrap_err!(profiles.delete_item(index))? { + drop(profiles); - let core = Core::global(); - log_if_err!(core.activate()); - } - Ok(()) + let core = Core::global(); + log_if_err!(core.activate()); + } + Ok(()) } /// patch the profile config #[tauri::command] pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); - wrap_err!(profiles.patch_item(index, profile))?; - drop(profiles); + let global = Data::global(); + let mut profiles = global.profiles.lock(); + wrap_err!(profiles.patch_item(index, profile))?; + drop(profiles); - // update cron task - let core = Core::global(); - let mut timer = core.timer.lock(); - wrap_err!(timer.refresh()) + // update cron task + let core = Core::global(); + 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) -> CmdResult { - let global = Data::global(); - let profiles = global.profiles.lock(); - let item = wrap_err!(profiles.get_item(&index))?; + let global = Data::global(); + let profiles = global.profiles.lock(); + let item = wrap_err!(profiles.get_item(&index))?; - let file = item.file.clone(); - if file.is_none() { - ret_err!("file is null"); - } + let file = item.file.clone(); + if file.is_none() { + ret_err!("file is null"); + } - let path = dirs::app_profiles_dir().join(file.unwrap()); - if !path.exists() { - ret_err!("file not found"); - } + let path = dirs::app_profiles_dir().join(file.unwrap()); + if !path.exists() { + ret_err!("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) -> CmdResult { - let global = Data::global(); - let profiles = global.profiles.lock(); - let item = wrap_err!(profiles.get_item(&index))?; - let data = wrap_err!(item.read_file())?; - Ok(data) + let global = Data::global(); + let profiles = global.profiles.lock(); + let item = wrap_err!(profiles.get_item(&index))?; + let data = wrap_err!(item.read_file())?; + Ok(data) } /// save the profile item file data #[tauri::command] pub fn save_profile_file(index: String, file_data: Option) -> CmdResult { - if file_data.is_none() { - return Ok(()); - } + if file_data.is_none() { + return Ok(()); + } - let global = Data::global(); - let profiles = global.profiles.lock(); - let item = wrap_err!(profiles.get_item(&index))?; - wrap_err!(item.save_file(file_data.unwrap())) + let global = Data::global(); + let profiles = global.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() -> CmdResult { - let global = Data::global(); - let clash = global.clash.lock(); - Ok(clash.info.clone()) + let global = Data::global(); + let clash = global.clash.lock(); + Ok(clash.info.clone()) } /// get the runtime clash config mapping #[tauri::command] pub fn get_runtime_config() -> CmdResult> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.config.clone()) + let core = Core::global(); + let rt = core.runtime.lock(); + Ok(rt.config.clone()) } /// get the runtime clash config yaml string #[tauri::command] pub fn get_runtime_yaml() -> CmdResult> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.config_yaml.clone()) + let core = Core::global(); + let rt = core.runtime.lock(); + Ok(rt.config_yaml.clone()) } /// get the runtime config exists keys #[tauri::command] pub fn get_runtime_exists() -> CmdResult> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.exists_keys.clone()) + let core = Core::global(); + let rt = core.runtime.lock(); + Ok(rt.exists_keys.clone()) } /// get the runtime enhanced chain log #[tauri::command] pub fn get_runtime_logs() -> CmdResult>> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.chain_logs.clone()) + let core = Core::global(); + let rt = core.runtime.lock(); + Ok(rt.chain_logs.clone()) } /// update the clash core config @@ -209,153 +209,153 @@ pub fn get_runtime_logs() -> CmdResult>> { /// then we should save the latest config #[tauri::command] pub fn patch_clash_config(payload: Mapping) -> CmdResult { - let core = Core::global(); - wrap_err!(core.patch_clash(payload)) + let core = Core::global(); + wrap_err!(core.patch_clash(payload)) } #[tauri::command] pub fn get_verge_config() -> CmdResult { - let global = Data::global(); - let verge = global.verge.lock(); - Ok(verge.clone()) + let global = Data::global(); + let verge = global.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) -> CmdResult { - let core = Core::global(); - wrap_err!(core.patch_verge(payload)) + let core = Core::global(); + wrap_err!(core.patch_verge(payload)) } #[tauri::command] pub fn update_hotkeys(hotkeys: Vec) -> CmdResult { - let core = Core::global(); - let mut hotkey = core.hotkey.lock(); - wrap_err!(hotkey.update(hotkeys)) + let core = Core::global(); + let mut hotkey = core.hotkey.lock(); + wrap_err!(hotkey.update(hotkeys)) } /// change clash core #[tauri::command] pub fn change_clash_core(clash_core: Option) -> CmdResult { - let core = Core::global(); - wrap_err!(core.change_core(clash_core)) + let core = Core::global(); + wrap_err!(core.change_core(clash_core)) } /// restart the sidecar #[tauri::command] pub fn restart_sidecar() -> CmdResult { - let core = Core::global(); - wrap_err!(core.restart_clash()) + let core = Core::global(); + wrap_err!(core.restart_clash()) } /// kill all sidecars when update app #[tauri::command] pub fn kill_sidecar() { - tauri::api::process::kill_children(); + tauri::api::process::kill_children(); } /// get the system proxy #[tauri::command] pub fn get_sys_proxy() -> CmdResult { - let current = wrap_err!(Sysproxy::get_system_proxy())?; + let current = wrap_err!(Sysproxy::get_system_proxy())?; - let mut map = Mapping::new(); - map.insert("enable".into(), current.enable.into()); - map.insert( - "server".into(), - format!("{}:{}", current.host, current.port).into(), - ); - map.insert("bypass".into(), current.bypass.into()); + let mut map = Mapping::new(); + map.insert("enable".into(), current.enable.into()); + map.insert( + "server".into(), + format!("{}:{}", current.host, current.port).into(), + ); + map.insert("bypass".into(), current.bypass.into()); - Ok(map) + Ok(map) } #[tauri::command] pub fn get_clash_logs() -> CmdResult> { - let core = Core::global(); - let service = core.service.lock(); - Ok(service.get_logs()) + let core = Core::global(); + let service = core.service.lock(); + Ok(service.get_logs()) } /// open app config dir #[tauri::command] pub fn open_app_dir() -> CmdResult<()> { - 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() -> CmdResult<()> { - 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)) } /// open url #[tauri::command] pub fn open_web_url(url: String) -> CmdResult<()> { - wrap_err!(open::that(url)) + wrap_err!(open::that(url)) } /// 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() -> CmdResult<()> { - wrap_err!(crate::core::Service::start_service().await) - } - - #[tauri::command] - pub async fn stop_service() -> CmdResult<()> { - wrap_err!(crate::core::Service::stop_service().await) - } - - #[tauri::command] - pub async fn check_service() -> CmdResult { - // no log - match crate::core::Service::check_service().await { - Ok(res) => Ok(res), - Err(err) => Err(err.to_string()), + #[tauri::command] + pub async fn start_service() -> CmdResult<()> { + wrap_err!(crate::core::Service::start_service().await) } - } - #[tauri::command] - pub async fn install_service() -> CmdResult<()> { - wrap_err!(crate::core::Service::install_service().await) - } + #[tauri::command] + pub async fn stop_service() -> CmdResult<()> { + wrap_err!(crate::core::Service::stop_service().await) + } - #[tauri::command] - pub async fn uninstall_service() -> CmdResult<()> { - wrap_err!(crate::core::Service::uninstall_service().await) - } + #[tauri::command] + pub async fn check_service() -> CmdResult { + // 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() -> CmdResult<()> { + wrap_err!(crate::core::Service::install_service().await) + } + + #[tauri::command] + pub async fn uninstall_service() -> CmdResult<()> { + 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() -> CmdResult<()> { - Ok(()) - } - #[tauri::command] - pub async fn stop_service() -> CmdResult<()> { - Ok(()) - } - #[tauri::command] - pub async fn check_service() -> CmdResult<()> { - Ok(()) - } - #[tauri::command] - pub async fn install_service() -> CmdResult<()> { - Ok(()) - } - #[tauri::command] - pub async fn uninstall_service() -> CmdResult<()> { - Ok(()) - } + #[tauri::command] + pub async fn start_service() -> CmdResult<()> { + Ok(()) + } + #[tauri::command] + pub async fn stop_service() -> CmdResult<()> { + Ok(()) + } + #[tauri::command] + pub async fn check_service() -> CmdResult<()> { + Ok(()) + } + #[tauri::command] + pub async fn install_service() -> CmdResult<()> { + Ok(()) + } + #[tauri::command] + pub async fn uninstall_service() -> CmdResult<()> { + Ok(()) + } } diff --git a/src-tauri/src/config/field.rs b/src-tauri/src/config/field.rs index 7105467..b7f52de 100644 --- a/src-tauri/src/config/field.rs +++ b/src-tauri/src/config/field.rs @@ -1,119 +1,119 @@ use serde_yaml::{Mapping, Value}; pub const HANDLE_FIELDS: [&str; 9] = [ - "port", - "socks-port", - "mixed-port", - "mode", - "ipv6", - "log-level", - "allow-lan", - "external-controller", - "secret", + "port", + "socks-port", + "mixed-port", + "mode", + "ipv6", + "log-level", + "allow-lan", + "external-controller", + "secret", ]; pub const DEFAULT_FIELDS: [&str; 5] = [ - "proxies", - "proxy-groups", - "rules", - "proxy-providers", - "rule-providers", + "proxies", + "proxy-groups", + "rules", + "proxy-providers", + "rule-providers", ]; pub const OTHERS_FIELDS: [&str; 21] = [ - "tun", - "dns", - "ebpf", - "hosts", - "script", - "profile", - "payload", - "auto-redir", - "experimental", - "interface-name", - "routing-mark", - "redir-port", - "tproxy-port", - "iptables", - "external-ui", - "bind-address", - "authentication", - "sniffer", // meta - "sub-rules", // meta - "geodata-mode", // meta - "tcp-concurrent", // meta + "tun", + "dns", + "ebpf", + "hosts", + "script", + "profile", + "payload", + "auto-redir", + "experimental", + "interface-name", + "routing-mark", + "redir-port", + "tproxy-port", + "iptables", + "external-ui", + "bind-address", + "authentication", + "sniffer", // meta + "sub-rules", // meta + "geodata-mode", // meta + "tcp-concurrent", // meta ]; pub fn use_clash_fields() -> Vec { - DEFAULT_FIELDS - .into_iter() - .chain(HANDLE_FIELDS) - .chain(OTHERS_FIELDS) - .map(|s| s.to_string()) - .collect() + DEFAULT_FIELDS + .into_iter() + .chain(HANDLE_FIELDS) + .chain(OTHERS_FIELDS) + .map(|s| s.to_string()) + .collect() } pub fn use_valid_fields(mut valid: Vec) -> Vec { - let others = Vec::from(OTHERS_FIELDS); + let others = Vec::from(OTHERS_FIELDS); - valid.iter_mut().for_each(|s| s.make_ascii_lowercase()); - valid - .into_iter() - .filter(|s| others.contains(&s.as_str())) - .chain(DEFAULT_FIELDS.iter().map(|s| s.to_string())) - .collect() + valid.iter_mut().for_each(|s| s.make_ascii_lowercase()); + valid + .into_iter() + .filter(|s| others.contains(&s.as_str())) + .chain(DEFAULT_FIELDS.iter().map(|s| s.to_string())) + .collect() } pub fn use_filter(config: Mapping, filter: &Vec) -> Mapping { - let mut ret = Mapping::new(); + let mut ret = Mapping::new(); - for (key, value) in config.into_iter() { - if let Some(key) = key.as_str() { - if filter.contains(&key.to_string()) { - ret.insert(Value::from(key), value); - } + for (key, value) in config.into_iter() { + if let Some(key) = key.as_str() { + if filter.contains(&key.to_string()) { + ret.insert(Value::from(key), value); + } + } } - } - ret + ret } pub fn use_lowercase(config: Mapping) -> Mapping { - let mut ret = Mapping::new(); + let mut ret = Mapping::new(); - for (key, value) in config.into_iter() { - if let Some(key_str) = key.as_str() { - let mut key_str = String::from(key_str); - key_str.make_ascii_lowercase(); - ret.insert(Value::from(key_str), value); + for (key, value) in config.into_iter() { + if let Some(key_str) = key.as_str() { + let mut key_str = String::from(key_str); + key_str.make_ascii_lowercase(); + ret.insert(Value::from(key_str), value); + } } - } - ret + ret } pub fn use_sort(config: Mapping) -> Mapping { - let mut ret = Mapping::new(); + let mut ret = Mapping::new(); - HANDLE_FIELDS - .into_iter() - .chain(OTHERS_FIELDS) - .chain(DEFAULT_FIELDS) - .for_each(|key| { - let key = Value::from(key); - config.get(&key).map(|value| { - ret.insert(key, value.clone()); - }); - }); - ret + HANDLE_FIELDS + .into_iter() + .chain(OTHERS_FIELDS) + .chain(DEFAULT_FIELDS) + .for_each(|key| { + let key = Value::from(key); + config.get(&key).map(|value| { + ret.insert(key, value.clone()); + }); + }); + ret } pub fn use_keys(config: &Mapping) -> Vec { - config - .iter() - .filter_map(|(key, _)| key.as_str()) - .map(|s| { - let mut s = s.to_string(); - s.make_ascii_lowercase(); - return s; - }) - .collect() + config + .iter() + .filter_map(|(key, _)| key.as_str()) + .map(|s| { + let mut s = s.to_string(); + s.make_ascii_lowercase(); + return s; + }) + .collect() } diff --git a/src-tauri/src/config/merge.rs b/src-tauri/src/config/merge.rs index 335d788..7f6b89d 100644 --- a/src-tauri/src/config/merge.rs +++ b/src-tauri/src/config/merge.rs @@ -3,60 +3,60 @@ use serde_yaml::{self, Mapping, Sequence, Value}; #[allow(unused)] const MERGE_FIELDS: [&str; 6] = [ - "prepend-rules", - "append-rules", - "prepend-proxies", - "append-proxies", - "prepend-proxy-groups", - "append-proxy-groups", + "prepend-rules", + "append-rules", + "prepend-proxies", + "append-proxies", + "prepend-proxy-groups", + "append-proxy-groups", ]; pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping { - // 直接覆盖原字段 - use_lowercase(merge.clone()) - .into_iter() - .for_each(|(key, value)| { - config.insert(key, value); - }); + // 直接覆盖原字段 + use_lowercase(merge.clone()) + .into_iter() + .for_each(|(key, value)| { + config.insert(key, value); + }); - let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string()); - let merge = use_filter(merge, &merge_list.collect()); + let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string()); + let merge = use_filter(merge, &merge_list.collect()); - ["rules", "proxies", "proxy-groups"] - .iter() - .for_each(|key_str| { - let key_val = Value::from(key_str.to_string()); + ["rules", "proxies", "proxy-groups"] + .iter() + .for_each(|key_str| { + let key_val = Value::from(key_str.to_string()); - let mut list = Sequence::default(); - list = config.get(&key_val).map_or(list.clone(), |val| { - val.as_sequence().map_or(list, |v| v.clone()) - }); + let mut list = Sequence::default(); + list = config.get(&key_val).map_or(list.clone(), |val| { + val.as_sequence().map_or(list, |v| v.clone()) + }); - let pre_key = Value::from(format!("prepend-{key_str}")); - let post_key = Value::from(format!("append-{key_str}")); + let pre_key = Value::from(format!("prepend-{key_str}")); + let post_key = Value::from(format!("append-{key_str}")); - if let Some(pre_val) = merge.get(&pre_key) { - if pre_val.is_sequence() { - let mut pre_val = pre_val.as_sequence().unwrap().clone(); - pre_val.extend(list); - list = pre_val; - } - } + if let Some(pre_val) = merge.get(&pre_key) { + if pre_val.is_sequence() { + let mut pre_val = pre_val.as_sequence().unwrap().clone(); + pre_val.extend(list); + list = pre_val; + } + } - if let Some(post_val) = merge.get(&post_key) { - if post_val.is_sequence() { - list.extend(post_val.as_sequence().unwrap().clone()); - } - } + if let Some(post_val) = merge.get(&post_key) { + if post_val.is_sequence() { + list.extend(post_val.as_sequence().unwrap().clone()); + } + } - config.insert(key_val, Value::from(list)); - }); - config + config.insert(key_val, Value::from(list)); + }); + config } #[test] fn test_merge() -> anyhow::Result<()> { - let merge = r" + let merge = r" prepend-rules: - prepend - 1123123 @@ -76,18 +76,18 @@ fn test_merge() -> anyhow::Result<()> { enable: true "; - let config = r" + let config = r" rules: - aaaaa script1: test "; - let merge = serde_yaml::from_str::(merge)?; - let config = serde_yaml::from_str::(config)?; + let merge = serde_yaml::from_str::(merge)?; + let config = serde_yaml::from_str::(config)?; - let result = serde_yaml::to_string(&use_merge(merge, config))?; + let result = serde_yaml::to_string(&use_merge(merge, config))?; - println!("{result}"); + println!("{result}"); - Ok(()) + Ok(()) } diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index 5bfca85..7020020 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -16,54 +16,54 @@ use std::collections::HashSet; type ResultLog = Vec<(String, String)>; pub fn enhance_config( - clash_config: Mapping, - profile_config: Mapping, - chain: Vec, - valid: Vec, - tun_mode: bool, + clash_config: Mapping, + profile_config: Mapping, + chain: Vec, + valid: Vec, + tun_mode: bool, ) -> (Mapping, Vec, HashMap) { - let mut config = profile_config; - let mut result_map = HashMap::new(); - let mut exists_keys = use_keys(&config); + let mut config = profile_config; + let mut result_map = HashMap::new(); + let mut exists_keys = use_keys(&config); - let valid = use_valid_fields(valid); + let valid = use_valid_fields(valid); - chain.into_iter().for_each(|item| match item.data { - ChainType::Merge(merge) => { - exists_keys.extend(use_keys(&merge)); - config = use_merge(merge, config.to_owned()); - config = use_filter(config.to_owned(), &valid); - } - ChainType::Script(script) => { - let mut logs = vec![]; - - match use_script(script, config.to_owned()) { - Ok((res_config, res_logs)) => { - exists_keys.extend(use_keys(&res_config)); - config = use_filter(res_config, &valid); - logs.extend(res_logs); + chain.into_iter().for_each(|item| match item.data { + ChainType::Merge(merge) => { + exists_keys.extend(use_keys(&merge)); + config = use_merge(merge, config.to_owned()); + config = use_filter(config.to_owned(), &valid); } - Err(err) => logs.push(("exception".into(), err.to_string())), - } + ChainType::Script(script) => { + let mut logs = vec![]; - result_map.insert(item.uid, logs); + match use_script(script, config.to_owned()) { + Ok((res_config, res_logs)) => { + exists_keys.extend(use_keys(&res_config)); + config = use_filter(res_config, &valid); + logs.extend(res_logs); + } + Err(err) => logs.push(("exception".into(), err.to_string())), + } + + result_map.insert(item.uid, logs); + } + }); + + config = use_filter(config, &valid); + + for (key, value) in clash_config.into_iter() { + config.insert(key, value); } - }); - config = use_filter(config, &valid); + let clash_fields = use_clash_fields(); + config = use_filter(config, &clash_fields); + config = use_tun(config, tun_mode); + config = use_sort(config); - for (key, value) in clash_config.into_iter() { - config.insert(key, value); - } + let mut exists_set = HashSet::new(); + exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s))); + exists_keys = exists_set.into_iter().collect(); - let clash_fields = use_clash_fields(); - config = use_filter(config, &clash_fields); - config = use_tun(config, tun_mode); - config = use_sort(config); - - let mut exists_set = HashSet::new(); - exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s))); - exists_keys = exists_set.into_iter().collect(); - - (config, exists_keys, result_map) + (config, exists_keys, result_map) } diff --git a/src-tauri/src/config/script.rs b/src-tauri/src/config/script.rs index bc7f001..97b34c2 100644 --- a/src-tauri/src/config/script.rs +++ b/src-tauri/src/config/script.rs @@ -3,66 +3,66 @@ use anyhow::Result; use serde_yaml::Mapping; pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> { - use rquickjs::{Context, Func, Runtime}; - use std::sync::{Arc, Mutex}; + use rquickjs::{Context, Func, Runtime}; + use std::sync::{Arc, Mutex}; - let runtime = Runtime::new().unwrap(); - let context = Context::full(&runtime).unwrap(); - let outputs = Arc::new(Mutex::new(vec![])); + let runtime = Runtime::new().unwrap(); + let context = Context::full(&runtime).unwrap(); + let outputs = Arc::new(Mutex::new(vec![])); - let copy_outputs = outputs.clone(); - let result = context.with(|ctx| -> Result { - ctx.globals().set( - "__verge_log__", - Func::from(move |level: String, data: String| { - let mut out = copy_outputs.lock().unwrap(); - out.push((level, data)); - }), - )?; + let copy_outputs = outputs.clone(); + let result = context.with(|ctx| -> Result { + ctx.globals().set( + "__verge_log__", + Func::from(move |level: String, data: String| { + let mut out = copy_outputs.lock().unwrap(); + out.push((level, data)); + }), + )?; - ctx.eval( - r#"var console = Object.freeze({ + ctx.eval( + r#"var console = Object.freeze({ log(data){__verge_log__("log",JSON.stringify(data))}, info(data){__verge_log__("info",JSON.stringify(data))}, error(data){__verge_log__("error",JSON.stringify(data))}, debug(data){__verge_log__("debug",JSON.stringify(data))}, });"#, - )?; + )?; - let config = use_lowercase(config.clone()); - let config_str = serde_json::to_string(&config)?; + let config = use_lowercase(config.clone()); + let config_str = serde_json::to_string(&config)?; - let code = format!( - r#"try{{ + let code = format!( + r#"try{{ {script}; JSON.stringify(main({config_str})||'') }} catch(err) {{ `__error_flag__ ${{err.toString()}}` }}"# - ); - let result: String = ctx.eval(code.as_str())?; - if result.starts_with("__error_flag__") { - anyhow::bail!(result[15..].to_owned()); - } - if result == "\"\"" { - anyhow::bail!("main function should return object"); - } - return Ok(serde_json::from_str::(result.as_str())?); - }); + ); + let result: String = ctx.eval(code.as_str())?; + if result.starts_with("__error_flag__") { + anyhow::bail!(result[15..].to_owned()); + } + if result == "\"\"" { + anyhow::bail!("main function should return object"); + } + return Ok(serde_json::from_str::(result.as_str())?); + }); - let mut out = outputs.lock().unwrap(); - match result { - Ok(config) => Ok((use_lowercase(config), out.to_vec())), - Err(err) => { - out.push(("exception".into(), err.to_string())); - Ok((config, out.to_vec())) + let mut out = outputs.lock().unwrap(); + match result { + Ok(config) => Ok((use_lowercase(config), out.to_vec())), + Err(err) => { + out.push(("exception".into(), err.to_string())); + Ok((config, out.to_vec())) + } } - } } #[test] fn test_script() { - let script = r#" + let script = r#" function main(config) { if (Array.isArray(config.rules)) { config.rules = [...config.rules, "add"]; @@ -73,7 +73,7 @@ fn test_script() { } "#; - let config = r#" + let config = r#" rules: - 111 - 222 @@ -83,12 +83,12 @@ fn test_script() { enable: false "#; - let config = serde_yaml::from_str(config).unwrap(); - let (config, results) = use_script(script.into(), config).unwrap(); + let config = serde_yaml::from_str(config).unwrap(); + let (config, results) = use_script(script.into(), config).unwrap(); - let config_str = serde_yaml::to_string(&config).unwrap(); + let config_str = serde_yaml::to_string(&config).unwrap(); - println!("{config_str}"); + println!("{config_str}"); - dbg!(results); + dbg!(results); } diff --git a/src-tauri/src/config/tun.rs b/src-tauri/src/config/tun.rs index 8c19f47..680d36f 100644 --- a/src-tauri/src/config/tun.rs +++ b/src-tauri/src/config/tun.rs @@ -1,81 +1,81 @@ use serde_yaml::{Mapping, Value}; macro_rules! revise { - ($map: expr, $key: expr, $val: expr) => { - let ret_key = Value::String($key.into()); - $map.insert(ret_key, Value::from($val)); - }; + ($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)); - } - }; + ($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)); + } + }; } pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { - let tun_key = Value::from("tun"); - let tun_val = config.get(&tun_key); + let tun_key = Value::from("tun"); + let tun_val = config.get(&tun_key); - if !enable && tun_val.is_none() { - return config; - } + if !enable && tun_val.is_none() { + return config; + } - let mut tun_val = tun_val.map_or(Mapping::new(), |val| { - val.as_mapping().cloned().unwrap_or(Mapping::new()) - }); + let mut tun_val = tun_val.map_or(Mapping::new(), |val| { + val.as_mapping().cloned().unwrap_or(Mapping::new()) + }); - revise!(tun_val, "enable", enable); - if enable { - append!(tun_val, "stack", "gvisor"); - append!(tun_val, "dns-hijack", vec!["198.18.0.2:53"]); - append!(tun_val, "auto-route", true); - append!(tun_val, "auto-detect-interface", true); - } + revise!(tun_val, "enable", enable); + if enable { + append!(tun_val, "stack", "gvisor"); + append!(tun_val, "dns-hijack", vec!["198.18.0.2:53"]); + append!(tun_val, "auto-route", true); + append!(tun_val, "auto-detect-interface", true); + } - revise!(config, "tun", tun_val); + revise!(config, "tun", tun_val); - if enable { - use_dns_for_tun(config) - } else { - config - } + if enable { + use_dns_for_tun(config) + } else { + config + } } fn use_dns_for_tun(mut config: Mapping) -> Mapping { - let dns_key = Value::from("dns"); - let dns_val = config.get(&dns_key); + let dns_key = Value::from("dns"); + let dns_val = config.get(&dns_key); - let mut dns_val = dns_val.map_or(Mapping::new(), |val| { - val.as_mapping().cloned().unwrap_or(Mapping::new()) - }); + let mut dns_val = dns_val.map_or(Mapping::new(), |val| { + val.as_mapping().cloned().unwrap_or(Mapping::new()) + }); - // 开启tun将同时开启dns - revise!(dns_val, "enable", true); + // 开启tun将同时开启dns + revise!(dns_val, "enable", true); - append!(dns_val, "enhanced-mode", "fake-ip"); - append!(dns_val, "fake-ip-range", "198.18.0.1/16"); - append!( - dns_val, - "nameserver", - vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"] - ); - append!(dns_val, "fallback", vec![] as Vec<&str>); + append!(dns_val, "enhanced-mode", "fake-ip"); + append!(dns_val, "fake-ip-range", "198.18.0.1/16"); + append!( + dns_val, + "nameserver", + vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"] + ); + append!(dns_val, "fallback", vec![] as Vec<&str>); - #[cfg(target_os = "windows")] - append!( - dns_val, - "fake-ip-filter", - vec![ - "dns.msftncsi.com", - "www.msftncsi.com", - "www.msftconnecttest.com" - ] - ); - revise!(config, "dns", dns_val); - config + #[cfg(target_os = "windows")] + append!( + dns_val, + "fake-ip-filter", + vec![ + "dns.msftncsi.com", + "www.msftncsi.com", + "www.msftconnecttest.com" + ] + ); + revise!(config, "dns", dns_val); + config } diff --git a/src-tauri/src/core/handle.rs b/src-tauri/src/core/handle.rs index f734536..dd04a48 100644 --- a/src-tauri/src/core/handle.rs +++ b/src-tauri/src/core/handle.rs @@ -5,62 +5,61 @@ use tauri::{AppHandle, Manager, Window}; #[derive(Debug, Default, Clone)] pub struct Handle { - pub app_handle: Option, + pub app_handle: Option, } impl Handle { - pub fn set_inner(&mut self, app_handle: AppHandle) { - self.app_handle = Some(app_handle); - } - - pub fn get_window(&self) -> Option { - self - .app_handle - .as_ref() - .map_or(None, |a| a.get_window("main")) - } - - pub fn refresh_clash(&self) { - if let Some(window) = self.get_window() { - log_if_err!(window.emit("verge://refresh-clash-config", "yes")); + pub fn set_inner(&mut self, app_handle: AppHandle) { + self.app_handle = Some(app_handle); } - } - pub fn refresh_verge(&self) { - if let Some(window) = self.get_window() { - log_if_err!(window.emit("verge://refresh-verge-config", "yes")); + pub fn get_window(&self) -> Option { + self.app_handle + .as_ref() + .map_or(None, |a| a.get_window("main")) } - } - #[allow(unused)] - pub fn refresh_profiles(&self) { - if let Some(window) = self.get_window() { - log_if_err!(window.emit("verge://refresh-profiles-config", "yes")); + pub fn refresh_clash(&self) { + if let Some(window) = self.get_window() { + log_if_err!(window.emit("verge://refresh-clash-config", "yes")); + } } - } - pub fn notice_message(&self, status: String, msg: String) { - if let Some(window) = self.get_window() { - log_if_err!(window.emit("verge://notice-message", (status, msg))); + pub fn refresh_verge(&self) { + if let Some(window) = self.get_window() { + log_if_err!(window.emit("verge://refresh-verge-config", "yes")); + } } - } - pub fn update_systray(&self) -> Result<()> { - if self.app_handle.is_none() { - bail!("update_systray unhandle error"); + #[allow(unused)] + pub fn refresh_profiles(&self) { + if let Some(window) = self.get_window() { + log_if_err!(window.emit("verge://refresh-profiles-config", "yes")); + } } - let app_handle = self.app_handle.as_ref().unwrap(); - Tray::update_systray(app_handle)?; - Ok(()) - } - /// update the system tray state - pub fn update_systray_part(&self) -> Result<()> { - if self.app_handle.is_none() { - bail!("update_systray unhandle error"); + pub fn notice_message(&self, status: String, msg: String) { + if let Some(window) = self.get_window() { + log_if_err!(window.emit("verge://notice-message", (status, msg))); + } + } + + pub fn update_systray(&self) -> Result<()> { + if self.app_handle.is_none() { + bail!("update_systray unhandle error"); + } + let app_handle = self.app_handle.as_ref().unwrap(); + Tray::update_systray(app_handle)?; + Ok(()) + } + + /// update the system tray state + pub fn update_systray_part(&self) -> Result<()> { + if self.app_handle.is_none() { + bail!("update_systray unhandle error"); + } + let app_handle = self.app_handle.as_ref().unwrap(); + Tray::update_part(app_handle)?; + Ok(()) } - let app_handle = self.app_handle.as_ref().unwrap(); - Tray::update_part(app_handle)?; - Ok(()) - } } diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 7446746..4ce4847 100644 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -4,150 +4,150 @@ use std::collections::HashMap; use tauri::{AppHandle, GlobalShortcutManager}; pub struct Hotkey { - current: Vec, // 保存当前的热键设置 - manager: Option, + current: Vec, // 保存当前的热键设置 + manager: Option, } impl Hotkey { - pub fn new() -> Hotkey { - Hotkey { - current: Vec::new(), - manager: None, - } - } - - pub fn init(&mut self, app_handle: AppHandle) -> Result<()> { - self.manager = Some(app_handle); - let data = Data::global(); - let verge = data.verge.lock(); - - if let Some(hotkeys) = verge.hotkeys.as_ref() { - for hotkey in hotkeys.iter() { - let mut iter = hotkey.split(','); - let func = iter.next(); - let key = iter.next(); - - if func.is_some() && key.is_some() { - log_if_err!(self.register(key.unwrap(), func.unwrap())); - } else { - log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None")); + pub fn new() -> Hotkey { + Hotkey { + current: Vec::new(), + manager: None, } - } - self.current = hotkeys.clone(); } - Ok(()) - } + pub fn init(&mut self, app_handle: AppHandle) -> Result<()> { + self.manager = Some(app_handle); + let data = Data::global(); + let verge = data.verge.lock(); - fn get_manager(&self) -> Result { - if self.manager.is_none() { - bail!("failed to get hotkey manager"); - } - Ok(self.manager.as_ref().unwrap().global_shortcut_manager()) - } + if let Some(hotkeys) = verge.hotkeys.as_ref() { + for hotkey in hotkeys.iter() { + let mut iter = hotkey.split(','); + let func = iter.next(); + let key = iter.next(); - fn register(&mut self, hotkey: &str, func: &str) -> Result<()> { - let mut manager = self.get_manager()?; - - if manager.is_registered(hotkey)? { - manager.unregister(hotkey)?; - } - - let f = match func.trim() { - "clash_mode_rule" => || feat::change_clash_mode("rule"), - "clash_mode_global" => || feat::change_clash_mode("global"), - "clash_mode_direct" => || feat::change_clash_mode("direct"), - "clash_mode_script" => || feat::change_clash_mode("script"), - "toggle_system_proxy" => || feat::toggle_system_proxy(), - "enable_system_proxy" => || feat::enable_system_proxy(), - "disable_system_proxy" => || feat::disable_system_proxy(), - "toggle_tun_mode" => || feat::toggle_tun_mode(), - "enable_tun_mode" => || feat::enable_tun_mode(), - "disable_tun_mode" => || feat::disable_tun_mode(), - - _ => bail!("invalid function \"{func}\""), - }; - - manager.register(hotkey, f)?; - log::info!(target: "app", "register hotkey {hotkey} {func}"); - Ok(()) - } - - fn unregister(&mut self, hotkey: &str) -> Result<()> { - self.get_manager()?.unregister(&hotkey)?; - log::info!(target: "app", "unregister hotkey {hotkey}"); - Ok(()) - } - - pub fn update(&mut self, new_hotkeys: Vec) -> Result<()> { - let current = self.current.to_owned(); - let old_map = Self::get_map_from_vec(¤t); - let new_map = Self::get_map_from_vec(&new_hotkeys); - - let (del, add) = Self::get_diff(old_map, new_map); - - del.iter().for_each(|key| { - let _ = self.unregister(key); - }); - - add.iter().for_each(|(key, func)| { - log_if_err!(self.register(key, func)); - }); - - self.current = new_hotkeys; - Ok(()) - } - - fn get_map_from_vec<'a>(hotkeys: &'a Vec) -> HashMap<&'a str, &'a str> { - let mut map = HashMap::new(); - - hotkeys.iter().for_each(|hotkey| { - let mut iter = hotkey.split(','); - let func = iter.next(); - let key = iter.next(); - - if func.is_some() && key.is_some() { - let func = func.unwrap().trim(); - let key = key.unwrap().trim(); - map.insert(key, func); - } - }); - map - } - - fn get_diff<'a>( - old_map: HashMap<&'a str, &'a str>, - new_map: HashMap<&'a str, &'a str>, - ) -> (Vec<&'a str>, Vec<(&'a str, &'a str)>) { - let mut del_list = vec![]; - let mut add_list = vec![]; - - old_map.iter().for_each(|(&key, func)| { - match new_map.get(key) { - Some(new_func) => { - if new_func != func { - del_list.push(key); - add_list.push((key, *new_func)); - } + if func.is_some() && key.is_some() { + log_if_err!(self.register(key.unwrap(), func.unwrap())); + } else { + log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None")); + } + } + self.current = hotkeys.clone(); } - None => del_list.push(key), - }; - }); - new_map.iter().for_each(|(&key, &func)| { - if old_map.get(key).is_none() { - add_list.push((key, func)); - } - }); + Ok(()) + } - (del_list, add_list) - } + fn get_manager(&self) -> Result { + if self.manager.is_none() { + bail!("failed to get hotkey manager"); + } + Ok(self.manager.as_ref().unwrap().global_shortcut_manager()) + } + + fn register(&mut self, hotkey: &str, func: &str) -> Result<()> { + let mut manager = self.get_manager()?; + + if manager.is_registered(hotkey)? { + manager.unregister(hotkey)?; + } + + let f = match func.trim() { + "clash_mode_rule" => || feat::change_clash_mode("rule"), + "clash_mode_global" => || feat::change_clash_mode("global"), + "clash_mode_direct" => || feat::change_clash_mode("direct"), + "clash_mode_script" => || feat::change_clash_mode("script"), + "toggle_system_proxy" => || feat::toggle_system_proxy(), + "enable_system_proxy" => || feat::enable_system_proxy(), + "disable_system_proxy" => || feat::disable_system_proxy(), + "toggle_tun_mode" => || feat::toggle_tun_mode(), + "enable_tun_mode" => || feat::enable_tun_mode(), + "disable_tun_mode" => || feat::disable_tun_mode(), + + _ => bail!("invalid function \"{func}\""), + }; + + manager.register(hotkey, f)?; + log::info!(target: "app", "register hotkey {hotkey} {func}"); + Ok(()) + } + + fn unregister(&mut self, hotkey: &str) -> Result<()> { + self.get_manager()?.unregister(&hotkey)?; + log::info!(target: "app", "unregister hotkey {hotkey}"); + Ok(()) + } + + pub fn update(&mut self, new_hotkeys: Vec) -> Result<()> { + let current = self.current.to_owned(); + let old_map = Self::get_map_from_vec(¤t); + let new_map = Self::get_map_from_vec(&new_hotkeys); + + let (del, add) = Self::get_diff(old_map, new_map); + + del.iter().for_each(|key| { + let _ = self.unregister(key); + }); + + add.iter().for_each(|(key, func)| { + log_if_err!(self.register(key, func)); + }); + + self.current = new_hotkeys; + Ok(()) + } + + fn get_map_from_vec<'a>(hotkeys: &'a Vec) -> HashMap<&'a str, &'a str> { + let mut map = HashMap::new(); + + hotkeys.iter().for_each(|hotkey| { + let mut iter = hotkey.split(','); + let func = iter.next(); + let key = iter.next(); + + if func.is_some() && key.is_some() { + let func = func.unwrap().trim(); + let key = key.unwrap().trim(); + map.insert(key, func); + } + }); + map + } + + fn get_diff<'a>( + old_map: HashMap<&'a str, &'a str>, + new_map: HashMap<&'a str, &'a str>, + ) -> (Vec<&'a str>, Vec<(&'a str, &'a str)>) { + let mut del_list = vec![]; + let mut add_list = vec![]; + + old_map.iter().for_each(|(&key, func)| { + match new_map.get(key) { + Some(new_func) => { + if new_func != func { + del_list.push(key); + add_list.push((key, *new_func)); + } + } + None => del_list.push(key), + }; + }); + + new_map.iter().for_each(|(&key, &func)| { + if old_map.get(key).is_none() { + add_list.push((key, func)); + } + }); + + (del_list, add_list) + } } impl Drop for Hotkey { - fn drop(&mut self) { - if let Ok(mut manager) = self.get_manager() { - let _ = manager.unregister_all(); + fn drop(&mut self) { + if let Ok(mut manager) = self.get_manager() { + let _ = manager.unregister_all(); + } } - } } diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 4cc30ac..c91c609 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -22,328 +22,328 @@ pub use self::service::*; #[derive(Clone)] pub struct Core { - pub service: Arc>, - pub sysopt: Arc>, - pub timer: Arc>, - pub hotkey: Arc>, - pub runtime: Arc>, - pub handle: Arc>, + pub service: Arc>, + pub sysopt: Arc>, + pub timer: Arc>, + pub hotkey: Arc>, + pub runtime: Arc>, + pub handle: Arc>, } impl Core { - pub fn global() -> &'static Core { - static CORE: OnceCell = OnceCell::new(); + pub fn global() -> &'static Core { + static CORE: OnceCell = OnceCell::new(); - CORE.get_or_init(|| Core { - service: Arc::new(Mutex::new(Service::new())), - sysopt: Arc::new(Mutex::new(Sysopt::new())), - timer: Arc::new(Mutex::new(Timer::new())), - hotkey: Arc::new(Mutex::new(Hotkey::new())), - runtime: Arc::new(Mutex::new(RuntimeResult::default())), - handle: Arc::new(Mutex::new(Handle::default())), - }) - } - - /// initialize the core state - pub fn init(&self, app_handle: tauri::AppHandle) { - // kill old clash process - Service::kill_old_clash(); - - let mut handle = self.handle.lock(); - handle.set_inner(app_handle.clone()); - drop(handle); - - let mut service = self.service.lock(); - log_if_err!(service.start()); - drop(service); - - log_if_err!(self.activate()); - - let mut sysopt = self.sysopt.lock(); - log_if_err!(sysopt.init_launch()); - log_if_err!(sysopt.init_sysproxy()); - drop(sysopt); - - let handle = self.handle.lock(); - log_if_err!(handle.update_systray_part()); - drop(handle); - - let mut hotkey = self.hotkey.lock(); - log_if_err!(hotkey.init(app_handle)); - drop(hotkey); - - // timer initialize - let mut timer = self.timer.lock(); - log_if_err!(timer.restore()); - } - - /// restart the clash sidecar - pub fn restart_clash(&self) -> Result<()> { - let mut service = self.service.lock(); - service.restart()?; - drop(service); - self.activate() - } - - /// 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}\""); + CORE.get_or_init(|| Core { + service: Arc::new(Mutex::new(Service::new())), + sysopt: Arc::new(Mutex::new(Sysopt::new())), + timer: Arc::new(Mutex::new(Timer::new())), + hotkey: Arc::new(Mutex::new(Hotkey::new())), + runtime: Arc::new(Mutex::new(RuntimeResult::default())), + handle: Arc::new(Mutex::new(Handle::default())), + }) } - let global = Data::global(); - let mut verge = global.verge.lock(); - verge.patch_config(Verge { - clash_core: Some(clash_core.clone()), - ..Verge::default() - })?; - drop(verge); + /// initialize the core state + pub fn init(&self, app_handle: tauri::AppHandle) { + // kill old clash process + Service::kill_old_clash(); - let mut service = self.service.lock(); - service.clear_logs(); - service.restart()?; - drop(service); + let mut handle = self.handle.lock(); + handle.set_inner(app_handle.clone()); + drop(handle); - self.activate() - } + let mut service = self.service.lock(); + log_if_err!(service.start()); + drop(service); - /// Patch Clash - /// handle the clash config changed - pub fn patch_clash(&self, patch: Mapping) -> Result<()> { - let patch_cloned = patch.clone(); - let clash_mode = patch.get("mode"); - let mixed_port = patch.get("mixed-port"); - let external = patch.get("external-controller"); - let secret = patch.get("secret"); + log_if_err!(self.activate()); - let valid_port = { - let global = Data::global(); - let mut clash = global.clash.lock(); - clash.patch_config(patch_cloned)?; - clash.info.port.is_some() - }; + let mut sysopt = self.sysopt.lock(); + log_if_err!(sysopt.init_launch()); + log_if_err!(sysopt.init_sysproxy()); + drop(sysopt); - // todo: port check - if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() { - let mut service = self.service.lock(); - service.restart()?; - drop(service); + let handle = self.handle.lock(); + log_if_err!(handle.update_systray_part()); + drop(handle); - self.activate()?; + let mut hotkey = self.hotkey.lock(); + log_if_err!(hotkey.init(app_handle)); + drop(hotkey); - let mut sysopt = self.sysopt.lock(); - sysopt.init_sysproxy()?; + // timer initialize + let mut timer = self.timer.lock(); + log_if_err!(timer.restore()); } - if clash_mode.is_some() { - let handle = self.handle.lock(); - handle.update_systray_part()?; - } - - Ok(()) - } - - /// Patch Verge - pub fn patch_verge(&self, patch: Verge) -> Result<()> { - // save the patch - let global = Data::global(); - let mut verge = global.verge.lock(); - verge.patch_config(patch.clone())?; - drop(verge); - - let tun_mode = patch.enable_tun_mode; - let auto_launch = patch.enable_auto_launch; - let system_proxy = patch.enable_system_proxy; - let proxy_bypass = patch.system_proxy_bypass; - let proxy_guard = patch.enable_proxy_guard; - let language = patch.language; - - #[cfg(target_os = "windows")] - { - let service_mode = patch.enable_service_mode; - - // 重启服务 - if service_mode.is_some() { + /// restart the clash sidecar + pub fn restart_clash(&self) -> Result<()> { let mut service = self.service.lock(); service.restart()?; drop(service); - } + self.activate() + } - if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) { - let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll"); - if !wintun_dll.exists() { - bail!("failed to enable TUN for missing `wintun.dll`"); + /// 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}\""); } - } - if service_mode.is_some() || tun_mode.is_some() { - self.activate()?; - } + let global = Data::global(); + let mut verge = global.verge.lock(); + verge.patch_config(Verge { + clash_core: Some(clash_core.clone()), + ..Verge::default() + })?; + drop(verge); + + let mut service = self.service.lock(); + service.clear_logs(); + service.restart()?; + drop(service); + + self.activate() } - #[cfg(not(target_os = "windows"))] - if tun_mode.is_some() { - self.activate()?; - } + /// Patch Clash + /// handle the clash config changed + pub fn patch_clash(&self, patch: Mapping) -> Result<()> { + let patch_cloned = patch.clone(); + let clash_mode = patch.get("mode"); + let mixed_port = patch.get("mixed-port"); + let external = patch.get("external-controller"); + let secret = patch.get("secret"); - let mut sysopt = self.sysopt.lock(); + let valid_port = { + let global = Data::global(); + let mut clash = global.clash.lock(); + clash.patch_config(patch_cloned)?; + clash.info.port.is_some() + }; - if auto_launch.is_some() { - sysopt.update_launch()?; - } - if system_proxy.is_some() || proxy_bypass.is_some() { - sysopt.update_sysproxy()?; - sysopt.guard_proxy(); - } - if proxy_guard.unwrap_or(false) { - sysopt.guard_proxy(); - } + // todo: port check + if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() { + let mut service = self.service.lock(); + service.restart()?; + drop(service); - // 更新tray - if language.is_some() { - let handle = self.handle.lock(); - handle.update_systray()?; - } else if system_proxy.is_some() || tun_mode.is_some() { - let handle = self.handle.lock(); - handle.update_systray_part()?; - } + self.activate()?; - if patch.hotkeys.is_some() { - let mut hotkey = self.hotkey.lock(); - hotkey.update(patch.hotkeys.unwrap())?; - } - - Ok(()) - } - - // update rule/global/direct/script mode - pub fn update_mode(&self, mode: &str) -> Result<()> { - // save config to file - let info = { - let global = Data::global(); - let mut clash = global.clash.lock(); - clash.config.insert(Value::from("mode"), Value::from(mode)); - clash.save_config()?; - clash.info.clone() - }; - - let mut mapping = Mapping::new(); - mapping.insert(Value::from("mode"), Value::from(mode)); - - let handle = self.handle.clone(); - - tauri::async_runtime::spawn(async move { - log_if_err!(Service::patch_config(info, mapping.to_owned()).await); - - // update tray - let handle = handle.lock(); - handle.refresh_clash(); - log_if_err!(handle.update_systray_part()); - }); - - Ok(()) - } - - /// activate the profile - /// auto activate enhanced profile - /// 触发clash配置更新 - pub fn activate(&self) -> Result<()> { - let global = Data::global(); - - let verge = global.verge.lock(); - let clash = global.clash.lock(); - let profiles = global.profiles.lock(); - - let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false); - let profile_activate = profiles.gen_activate()?; - - let clash_config = clash.config.clone(); - let clash_info = clash.info.clone(); - - drop(clash); - drop(verge); - drop(profiles); - - let (config, exists_keys, logs) = enhance_config( - clash_config, - profile_activate.current, - profile_activate.chain, - profile_activate.valid, - tun_mode, - ); - - let mut runtime = self.runtime.lock(); - *runtime = RuntimeResult { - config: Some(config.clone()), - config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())), - exists_keys, - chain_logs: logs, - }; - drop(runtime); - - let mut service = self.service.lock(); - service.check_start()?; - drop(service); - - let handle = self.handle.clone(); - tauri::async_runtime::spawn(async move { - match Service::set_config(clash_info, config).await { - Ok(_) => { - let handle = handle.lock(); - handle.refresh_clash(); - handle.notice_message("set_config::ok".into(), "ok".into()); + let mut sysopt = self.sysopt.lock(); + sysopt.init_sysproxy()?; } - Err(err) => { - let handle = handle.lock(); - handle.notice_message("set_config::error".into(), format!("{err}")); - log::error!(target: "app", "{err}") + + if clash_mode.is_some() { + let handle = self.handle.lock(); + handle.update_systray_part()?; } - } - }); - Ok(()) - } + Ok(()) + } - /// Static function - /// update profile item - pub async fn update_profile_item(&self, uid: String, option: Option) -> Result<()> { - let global = Data::global(); + /// Patch Verge + pub fn patch_verge(&self, patch: Verge) -> Result<()> { + // save the patch + let global = Data::global(); + let mut verge = global.verge.lock(); + verge.patch_config(patch.clone())?; + drop(verge); - let (url, opt) = { - let profiles = global.profiles.lock(); - let item = profiles.get_item(&uid)?; + let tun_mode = patch.enable_tun_mode; + let auto_launch = patch.enable_auto_launch; + let system_proxy = patch.enable_system_proxy; + let proxy_bypass = patch.system_proxy_bypass; + let proxy_guard = patch.enable_proxy_guard; + let language = patch.language; - 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() { + #[cfg(target_os = "windows")] + { + let service_mode = patch.enable_service_mode; + + // 重启服务 + if service_mode.is_some() { + let mut service = self.service.lock(); + service.restart()?; + drop(service); + } + + if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) { + let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll"); + if !wintun_dll.exists() { + bail!("failed to enable TUN for missing `wintun.dll`"); + } + } + + if service_mode.is_some() || tun_mode.is_some() { + self.activate()?; + } + } + + #[cfg(not(target_os = "windows"))] + if tun_mode.is_some() { + self.activate()?; + } + + let mut sysopt = self.sysopt.lock(); + + if auto_launch.is_some() { + sysopt.update_launch()?; + } + if system_proxy.is_some() || proxy_bypass.is_some() { + sysopt.update_sysproxy()?; + sysopt.guard_proxy(); + } + if proxy_guard.unwrap_or(false) { + sysopt.guard_proxy(); + } + + // 更新tray + if language.is_some() { + let handle = self.handle.lock(); + handle.update_systray()?; + } else if system_proxy.is_some() || tun_mode.is_some() { + let handle = self.handle.lock(); + handle.update_systray_part()?; + } + + if patch.hotkeys.is_some() { + let mut hotkey = self.hotkey.lock(); + hotkey.update(patch.hotkeys.unwrap())?; + } + + Ok(()) + } + + // update rule/global/direct/script mode + pub fn update_mode(&self, mode: &str) -> Result<()> { + // save config to file + let info = { + let global = Data::global(); + let mut clash = global.clash.lock(); + clash.config.insert(Value::from("mode"), Value::from(mode)); + clash.save_config()?; + clash.info.clone() + }; + + let mut mapping = Mapping::new(); + mapping.insert(Value::from("mode"), Value::from(mode)); + + let handle = self.handle.clone(); + + tauri::async_runtime::spawn(async move { + log_if_err!(Service::patch_config(info, mapping.to_owned()).await); + + // update tray + let handle = handle.lock(); + handle.refresh_clash(); + log_if_err!(handle.update_systray_part()); + }); + + Ok(()) + } + + /// activate the profile + /// auto activate enhanced profile + /// 触发clash配置更新 + pub fn activate(&self) -> Result<()> { + let global = Data::global(); + + let verge = global.verge.lock(); + let clash = global.clash.lock(); + let profiles = global.profiles.lock(); + + let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false); + let profile_activate = profiles.gen_activate()?; + + let clash_config = clash.config.clone(); + let clash_info = clash.info.clone(); + + drop(clash); + drop(verge); + drop(profiles); + + let (config, exists_keys, logs) = enhance_config( + clash_config, + profile_activate.current, + profile_activate.chain, + profile_activate.valid, + tun_mode, + ); + + let mut runtime = self.runtime.lock(); + *runtime = RuntimeResult { + config: Some(config.clone()), + config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())), + exists_keys, + chain_logs: logs, + }; + drop(runtime); + + let mut service = self.service.lock(); + service.check_start()?; + drop(service); + + let handle = self.handle.clone(); + tauri::async_runtime::spawn(async move { + match Service::set_config(clash_info, config).await { + Ok(_) => { + let handle = handle.lock(); + handle.refresh_clash(); + handle.notice_message("set_config::ok".into(), "ok".into()); + } + Err(err) => { + let handle = handle.lock(); + handle.notice_message("set_config::error".into(), format!("{err}")); + log::error!(target: "app", "{err}") + } + } + }); + + Ok(()) + } + + /// Static function + /// update profile item + pub async fn update_profile_item(&self, uid: String, option: Option) -> Result<()> { + let global = Data::global(); + + let (url, opt) = { + let profiles = global.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); + self.activate()?; + } + 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 = global.profiles.lock(); + profiles.update_item(uid.clone(), item)?; + + // reactivate the profile + if Some(uid) == profiles.get_current() { drop(profiles); self.activate()?; - } - 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 = global.profiles.lock(); - profiles.update_item(uid.clone(), item)?; - - // reactivate the profile - if Some(uid) == profiles.get_current() { - drop(profiles); - self.activate()?; + Ok(()) } - - Ok(()) - } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 8cc2ed7..21fb7ed 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -9,8 +9,8 @@ use std::fs; use std::io::Write; use std::sync::Arc; use std::{ - collections::{HashMap, VecDeque}, - time::Duration, + collections::{HashMap, VecDeque}, + time::Duration, }; use tauri::api::process::{Command, CommandChild, CommandEvent}; use tokio::time::sleep; @@ -19,209 +19,209 @@ const LOGS_QUEUE_LEN: usize = 100; #[derive(Debug)] pub struct Service { - sidecar: Option, + sidecar: Option, - logs: Arc>>, + logs: Arc>>, - #[allow(unused)] - use_service_mode: bool, + #[allow(unused)] + use_service_mode: bool, } impl Service { - pub fn new() -> Service { - let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10); + pub fn new() -> Service { + let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10); - Service { - sidecar: None, - logs: Arc::new(RwLock::new(queue)), - use_service_mode: false, - } - } - - pub fn start(&mut self) -> Result<()> { - #[cfg(not(target_os = "windows"))] - self.start_clash_by_sidecar()?; - - #[cfg(target_os = "windows")] - { - let enable = { - let data = Data::global(); - let verge = data.verge.lock(); - verge.enable_service_mode.clone().unwrap_or(false) - }; - - self.use_service_mode = enable; - - if !enable { - return self.start_clash_by_sidecar(); - } - - tauri::async_runtime::spawn(async move { - match Self::check_service().await { - Ok(status) => { - // 未启动clash - if status.code != 0 { - log_if_err!(Self::start_clash_by_service().await); - } - } - Err(err) => log::error!(target: "app", "{err}"), + Service { + sidecar: None, + logs: Arc::new(RwLock::new(queue)), + use_service_mode: false, } - }); } - Ok(()) - } + pub fn start(&mut self) -> Result<()> { + #[cfg(not(target_os = "windows"))] + self.start_clash_by_sidecar()?; - pub fn stop(&mut self) -> Result<()> { - #[cfg(not(target_os = "windows"))] - self.stop_clash_by_sidecar()?; + #[cfg(target_os = "windows")] + { + let enable = { + let data = Data::global(); + let verge = data.verge.lock(); + verge.enable_service_mode.clone().unwrap_or(false) + }; - #[cfg(target_os = "windows")] - { - let _ = self.stop_clash_by_sidecar(); + self.use_service_mode = enable; - if self.use_service_mode { - tauri::async_runtime::block_on(async move { - log_if_err!(Self::stop_clash_by_service().await); - }); - } + if !enable { + return self.start_clash_by_sidecar(); + } + + tauri::async_runtime::spawn(async move { + match Self::check_service().await { + Ok(status) => { + // 未启动clash + if status.code != 0 { + log_if_err!(Self::start_clash_by_service().await); + } + } + Err(err) => log::error!(target: "app", "{err}"), + } + }); + } + + Ok(()) } - Ok(()) - } + pub fn stop(&mut self) -> Result<()> { + #[cfg(not(target_os = "windows"))] + self.stop_clash_by_sidecar()?; - pub fn restart(&mut self) -> Result<()> { - self.stop()?; - self.start() - } + #[cfg(target_os = "windows")] + { + let _ = self.stop_clash_by_sidecar(); - pub fn get_logs(&self) -> VecDeque { - self.logs.read().clone() - } + if self.use_service_mode { + tauri::async_runtime::block_on(async move { + log_if_err!(Self::stop_clash_by_service().await); + }); + } + } - #[allow(unused)] - pub fn set_logs(&self, text: String) { - let mut logs = self.logs.write(); - if logs.len() > LOGS_QUEUE_LEN { - (*logs).pop_front(); - } - (*logs).push_back(text); - } - - pub fn clear_logs(&self) { - let mut logs = self.logs.write(); - (*logs).clear(); - } - - /// start the clash sidecar - fn start_clash_by_sidecar(&mut self) -> Result<()> { - if self.sidecar.is_some() { - let sidecar = self.sidecar.take().unwrap(); - let _ = sidecar.kill(); + Ok(()) } - let clash_core: String = { - let global = Data::global(); - let verge = global.verge.lock(); - verge.clash_core.clone().unwrap_or("clash".into()) - }; + pub fn restart(&mut self) -> Result<()> { + self.stop()?; + self.start() + } - let app_dir = dirs::app_home_dir(); - let app_dir = app_dir.as_os_str().to_str().unwrap(); + pub fn get_logs(&self) -> VecDeque { + self.logs.read().clone() + } - // fix #212 - let args = match clash_core.as_str() { - "clash-meta" => vec!["-m", "-d", app_dir], - _ => vec!["-d", app_dir], - }; - - let cmd = Command::new_sidecar(clash_core)?; - - let (mut rx, cmd_child) = cmd.args(args).spawn()?; - - // 将pid写入文件中 - let pid = cmd_child.pid(); - log_if_err!(|| -> Result<()> { - let path = dirs::clash_pid_path(); - fs::File::create(path)?.write(format!("{pid}").as_bytes())?; - Ok(()) - }()); - - self.sidecar = Some(cmd_child); - - // clash log - let logs = self.logs.clone(); - tauri::async_runtime::spawn(async move { - let write_log = |text: String| { - let mut logs = logs.write(); - if logs.len() >= LOGS_QUEUE_LEN { - (*logs).pop_front(); + #[allow(unused)] + pub fn set_logs(&self, text: String) { + let mut logs = self.logs.write(); + if logs.len() > LOGS_QUEUE_LEN { + (*logs).pop_front(); } (*logs).push_back(text); - }; + } - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Stdout(line) => { - let can_short = line.starts_with("time=") && line.len() > 33; - let stdout = if can_short { &line[33..] } else { &line }; - log::info!(target: "app" ,"[clash]: {}", stdout); - write_log(line); - } - CommandEvent::Stderr(err) => { - log::error!(target: "app" ,"[clash error]: {}", err); - write_log(err); - } - CommandEvent::Error(err) => log::error!(target: "app" ,"{err}"), - CommandEvent::Terminated(_) => break, - _ => {} + pub fn clear_logs(&self) { + let mut logs = self.logs.write(); + (*logs).clear(); + } + + /// start the clash sidecar + fn start_clash_by_sidecar(&mut self) -> Result<()> { + if self.sidecar.is_some() { + let sidecar = self.sidecar.take().unwrap(); + let _ = sidecar.kill(); } - } - }); - Ok(()) - } + let clash_core: String = { + let global = Data::global(); + let verge = global.verge.lock(); + verge.clash_core.clone().unwrap_or("clash".into()) + }; - /// stop the clash sidecar - fn stop_clash_by_sidecar(&mut self) -> Result<()> { - if let Some(sidecar) = self.sidecar.take() { - sidecar.kill()?; - } - Ok(()) - } + let app_dir = dirs::app_home_dir(); + let app_dir = app_dir.as_os_str().to_str().unwrap(); - pub fn check_start(&mut self) -> Result<()> { - #[cfg(target_os = "windows")] - { - let global = Data::global(); - let verge = global.verge.lock(); - let service_mode = verge.enable_service_mode.unwrap_or(false); + // fix #212 + let args = match clash_core.as_str() { + "clash-meta" => vec!["-m", "-d", app_dir], + _ => vec!["-d", app_dir], + }; - if !service_mode && self.sidecar.is_none() { - self.start()?; - } + let cmd = Command::new_sidecar(clash_core)?; + + let (mut rx, cmd_child) = cmd.args(args).spawn()?; + + // 将pid写入文件中 + let pid = cmd_child.pid(); + log_if_err!(|| -> Result<()> { + let path = dirs::clash_pid_path(); + fs::File::create(path)?.write(format!("{pid}").as_bytes())?; + Ok(()) + }()); + + self.sidecar = Some(cmd_child); + + // clash log + let logs = self.logs.clone(); + tauri::async_runtime::spawn(async move { + let write_log = |text: String| { + let mut logs = logs.write(); + if logs.len() >= LOGS_QUEUE_LEN { + (*logs).pop_front(); + } + (*logs).push_back(text); + }; + + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line) => { + let can_short = line.starts_with("time=") && line.len() > 33; + let stdout = if can_short { &line[33..] } else { &line }; + log::info!(target: "app" ,"[clash]: {}", stdout); + write_log(line); + } + CommandEvent::Stderr(err) => { + log::error!(target: "app" ,"[clash error]: {}", err); + write_log(err); + } + CommandEvent::Error(err) => log::error!(target: "app" ,"{err}"), + CommandEvent::Terminated(_) => break, + _ => {} + } + } + }); + + Ok(()) } - #[cfg(not(target_os = "windows"))] - if self.sidecar.is_none() { - self.start()?; + /// stop the clash sidecar + fn stop_clash_by_sidecar(&mut self) -> Result<()> { + if let Some(sidecar) = self.sidecar.take() { + sidecar.kill()?; + } + Ok(()) } - Ok(()) - } + pub fn check_start(&mut self) -> Result<()> { + #[cfg(target_os = "windows")] + { + let global = Data::global(); + let verge = global.verge.lock(); + let service_mode = verge.enable_service_mode.unwrap_or(false); - /// update clash config - /// using PUT methods - pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> { - let temp_path = dirs::profiles_temp_path(); - config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?; + if !service_mode && self.sidecar.is_none() { + self.start()?; + } + } - let (server, headers) = Self::clash_client_info(info)?; + #[cfg(not(target_os = "windows"))] + if self.sidecar.is_none() { + self.start()?; + } - let mut data = HashMap::new(); - data.insert("path", temp_path.as_os_str().to_str().unwrap()); + Ok(()) + } - macro_rules! report_err { + /// update clash config + /// using PUT methods + pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> { + let temp_path = dirs::profiles_temp_path(); + config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?; + + let (server, headers) = Self::clash_client_info(info)?; + + let mut data = HashMap::new(); + data.insert("path", temp_path.as_os_str().to_str().unwrap()); + + macro_rules! report_err { ($i: expr, $e: expr) => { match $i { 4 => bail!($e), @@ -230,306 +230,308 @@ impl Service { }; } - // retry 5 times - for i in 0..5 { - let headers = headers.clone(); - match reqwest::ClientBuilder::new().no_proxy().build() { - Ok(client) => { - let builder = client.put(&server).headers(headers).json(&data); - match builder.send().await { - Ok(resp) => match resp.status().as_u16() { - 204 => break, - // 配置有问题不重试 - 400 => bail!("failed to update clash config with status 400"), - status @ _ => report_err!(i, "failed to activate clash with status \"{status}\""), - }, - Err(err) => report_err!(i, "{err}"), - } + // retry 5 times + for i in 0..5 { + let headers = headers.clone(); + match reqwest::ClientBuilder::new().no_proxy().build() { + Ok(client) => { + let builder = client.put(&server).headers(headers).json(&data); + match builder.send().await { + Ok(resp) => match resp.status().as_u16() { + 204 => break, + // 配置有问题不重试 + 400 => bail!("failed to update clash config with status 400"), + status @ _ => { + report_err!(i, "failed to activate clash with status \"{status}\"") + } + }, + Err(err) => report_err!(i, "{err}"), + } + } + Err(err) => report_err!(i, "{err}"), + } + sleep(Duration::from_millis(500)).await; } - Err(err) => report_err!(i, "{err}"), - } - sleep(Duration::from_millis(500)).await; + + Ok(()) } - Ok(()) - } + /// patch clash config + pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> { + let (server, headers) = Self::clash_client_info(info)?; - /// patch clash config - pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> { - let (server, headers) = Self::clash_client_info(info)?; - - let client = reqwest::ClientBuilder::new().no_proxy().build()?; - let builder = client.patch(&server).headers(headers.clone()).json(&config); - builder.send().await?; - Ok(()) - } - - /// get clash client url and headers from clash info - fn clash_client_info(info: ClashInfo) -> Result<(String, HeaderMap)> { - if info.server.is_none() { - let status = &info.status; - if info.port.is_none() { - bail!("failed to parse config.yaml file with status {status}"); - } else { - bail!("failed to parse the server with status {status}"); - } + let client = reqwest::ClientBuilder::new().no_proxy().build()?; + let builder = client.patch(&server).headers(headers.clone()).json(&config); + builder.send().await?; + Ok(()) } - 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); - } - - Ok((server, headers)) - } - - /// kill old clash process - pub fn kill_old_clash() { - use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; - - if let Ok(pid) = fs::read(dirs::clash_pid_path()) { - if let Ok(pid) = String::from_utf8_lossy(&pid).parse() { - let mut system = System::new(); - system.refresh_all(); - - let proc = system.process(Pid::from_u32(pid)); - if let Some(proc) = proc { - proc.kill(); + /// get clash client url and headers from clash info + fn clash_client_info(info: ClashInfo) -> Result<(String, HeaderMap)> { + if info.server.is_none() { + let status = &info.status; + if info.port.is_none() { + bail!("failed to parse config.yaml file with status {status}"); + } else { + bail!("failed to parse the server with status {status}"); + } + } + + 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); + } + + Ok((server, headers)) + } + + /// kill old clash process + pub fn kill_old_clash() { + use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; + + if let Ok(pid) = fs::read(dirs::clash_pid_path()) { + if let Ok(pid) = String::from_utf8_lossy(&pid).parse() { + let mut system = System::new(); + system.refresh_all(); + + let proc = system.process(Pid::from_u32(pid)); + if let Some(proc) = proc { + proc.kill(); + } + } } - } } - } } 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(target_os = "windows")] pub mod win_service { - use super::*; - use anyhow::Context; - use deelevate::{PrivilegeLevel, Token}; - use runas::Command as RunasCommand; - use serde::{Deserialize, Serialize}; - 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 serde::{Deserialize, Serialize}; + 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 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 ResponseBody { + pub bin_path: String, + pub config_dir: String, + pub log_file: String, } - /// 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(()) + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct JsonResponse { + pub code: u64, + pub msg: String, + pub data: Option, } - /// [deprecated] - /// start service - /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 - pub async fn start_service() -> Result<()> { - let token = Token::with_current_process()?; - let level = token.privilege_level()?; + 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"); - let args = ["start", SERVICE_NAME]; + if !install_path.exists() { + bail!("installer exe not found"); + } - let status = match level { - PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?, - _ => StdCommand::new("sc").args(&args).status()?, - }; + let token = Token::with_current_process()?; + let level = token.privilege_level()?; - match status.success() { - true => Ok(()), - false => bail!( - "failed to start service with status {}", - status.code().unwrap() - ), - } + 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 = { + let global = Data::global(); + let verge = global.verge.lock(); + verge.clash_core.clone().unwrap_or("clash".into()) + }; + + 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(()) + } } - - /// 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 = { - let global = Data::global(); - let verge = global.verge.lock(); - verge.clash_core.clone().unwrap_or("clash".into()) - }; - - 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/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index d2a5e4f..53bf939 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -6,18 +6,18 @@ use sysproxy::Sysproxy; use tauri::{async_runtime::Mutex, utils::platform::current_exe}; pub struct Sysopt { - /// current system proxy setting - cur_sysproxy: Option, + /// current system proxy setting + cur_sysproxy: Option, - /// record the original system proxy - /// recover it when exit - old_sysproxy: Option, + /// record the original system proxy + /// recover it when exit + old_sysproxy: Option, - /// helps to auto launch the app - auto_launch: Option, + /// helps to auto launch the app + auto_launch: Option, - /// record whether the guard async is running or not - guard_state: Arc>, + /// record whether the guard async is running or not + guard_state: Arc>, } #[cfg(target_os = "windows")] @@ -28,258 +28,258 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1"; static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,"; 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) -> Result<()> { - let data = Data::global(); - let clash = data.clash.lock(); - let port = clash.info.port.clone(); - - if port.is_none() { - bail!("clash port is none"); + pub fn new() -> Sysopt { + Sysopt { + cur_sysproxy: None, + old_sysproxy: None, + auto_launch: None, + guard_state: Arc::new(Mutex::new(false)), + } } - let verge = data.verge.lock(); + /// init the sysproxy + pub fn init_sysproxy(&mut self) -> Result<()> { + let data = Data::global(); + let clash = data.clash.lock(); + let port = clash.info.port.clone(); - let enable = verge.enable_system_proxy.clone().unwrap_or(false); - let bypass = verge.system_proxy_bypass.clone(); - let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into()); + if port.is_none() { + bail!("clash port is none"); + } - let port = port.unwrap().parse::()?; - let host = String::from("127.0.0.1"); - - self.cur_sysproxy = Some(Sysproxy { - enable, - host, - port, - bypass, - }); - - if enable { - self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p)); - self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?; - } - - // run the system proxy guard - self.guard_proxy(); - Ok(()) - } - - /// update the system proxy - pub fn update_sysproxy(&mut self) -> Result<()> { - if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() { - return self.init_sysproxy(); - } - - let data = Data::global(); - let verge = data.verge.lock(); - - let enable = verge.enable_system_proxy.clone().unwrap_or(false); - let bypass = verge.system_proxy_bypass.clone(); - let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into()); - - let mut sysproxy = self.cur_sysproxy.take().unwrap(); - - sysproxy.enable = enable; - sysproxy.bypass = bypass; - - self.cur_sysproxy = Some(sysproxy); - self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?; - - Ok(()) - } - - /// reset the sysproxy - pub fn reset_sysproxy(&mut self) -> Result<()> { - let cur = self.cur_sysproxy.take(); - - if let Some(mut old) = self.old_sysproxy.take() { - // 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置 - // 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了 - let port_same = cur.map_or(true, |cur| old.port == cur.port); - - if old.enable && port_same { - old.enable = false; - log::info!(target: "app", "reset proxy by disabling the original proxy"); - } else { - log::info!(target: "app", "reset proxy to the original proxy"); - } - - old.set_system_proxy()?; - } else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur { - // 没有原代理,就按现在的代理设置disable即可 - log::info!(target: "app", "reset proxy by disabling the current proxy"); - cur.enable = false; - cur.set_system_proxy()?; - } else { - log::info!(target: "app", "reset proxy with no action"); - } - - Ok(()) - } - - /// init the auto launch - pub fn init_launch(&mut self) -> Result<()> { - let data = Data::global(); - let verge = data.verge.lock(); - let enable = verge.enable_auto_launch.clone().unwrap_or(false); - - let app_exe = current_exe()?; - let app_exe = dunce::canonicalize(app_exe)?; - let app_name = app_exe - .file_stem() - .and_then(|f| f.to_str()) - .ok_or(anyhow!("failed to get file stem"))?; - - let app_path = app_exe - .as_os_str() - .to_str() - .ok_or(anyhow!("failed to get app_path"))? - .to_string(); - - // fix issue #26 - #[cfg(target_os = "windows")] - let app_path = format!("\"{app_path}\""); - - // use the /Applications/Clash Verge.app path - #[cfg(target_os = "macos")] - let app_path = (|| -> Option { - let path = std::path::PathBuf::from(&app_path); - let path = path.parent()?.parent()?.parent()?; - let extension = path.extension()?.to_str()?; - match extension == "app" { - true => Some(path.as_os_str().to_str()?.to_string()), - false => None, - } - })() - .unwrap_or(app_path); - - let auto = AutoLaunchBuilder::new() - .set_app_name(app_name) - .set_app_path(&app_path) - .build()?; - - self.auto_launch = Some(auto); - - // 避免在开发时将自启动关了 - #[cfg(feature = "verge-dev")] - if !enable { - return Ok(()); - } - - let auto = self.auto_launch.as_ref().unwrap(); - - // macos每次启动都更新登录项,避免重复设置登录项 - #[cfg(target_os = "macos")] - { - let _ = auto.disable(); - if enable { - auto.enable()?; - } - } - - #[cfg(not(target_os = "macos"))] - { - match enable { - true => auto.enable()?, - false => auto.disable()?, - }; - } - - Ok(()) - } - - /// update the startup - pub fn update_launch(&mut self) -> Result<()> { - if self.auto_launch.is_none() { - return self.init_launch(); - } - - let data = Data::global(); - let verge = data.verge.lock(); - let enable = verge.enable_auto_launch.clone().unwrap_or(false); - - let auto_launch = self.auto_launch.as_ref().unwrap(); - - match enable { - true => auto_launch.enable()?, - false => crate::log_if_err!(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; - - let global = Data::global(); - let verge = global.verge.lock(); + let verge = data.verge.lock(); let enable = verge.enable_system_proxy.clone().unwrap_or(false); - let guard = verge.enable_proxy_guard.clone().unwrap_or(false); - let guard_duration = verge.proxy_guard_duration.clone().unwrap_or(10); let bypass = verge.system_proxy_bypass.clone(); - drop(verge); + let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into()); - // stop loop - if !enable || !guard { - break; + let port = port.unwrap().parse::()?; + let host = String::from("127.0.0.1"); + + self.cur_sysproxy = Some(Sysproxy { + enable, + host, + port, + bypass, + }); + + if enable { + self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p)); + self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?; } - // update duration - wait_secs = guard_duration; + // run the system proxy guard + self.guard_proxy(); + Ok(()) + } - let clash = global.clash.lock(); - let port = clash.info.port.clone(); - let port = port.unwrap_or("".into()).parse::(); - drop(clash); + /// update the system proxy + pub fn update_sysproxy(&mut self) -> Result<()> { + if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() { + return self.init_sysproxy(); + } - log::debug!(target: "app", "try to guard the system proxy"); + let data = Data::global(); + let verge = data.verge.lock(); - match port { - Ok(port) => { - let sysproxy = Sysproxy { - enable: true, - host: "127.0.0.1".into(), - port, - bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), + let enable = verge.enable_system_proxy.clone().unwrap_or(false); + let bypass = verge.system_proxy_bypass.clone(); + let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into()); + + let mut sysproxy = self.cur_sysproxy.take().unwrap(); + + sysproxy.enable = enable; + sysproxy.bypass = bypass; + + self.cur_sysproxy = Some(sysproxy); + self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?; + + Ok(()) + } + + /// reset the sysproxy + pub fn reset_sysproxy(&mut self) -> Result<()> { + let cur = self.cur_sysproxy.take(); + + if let Some(mut old) = self.old_sysproxy.take() { + // 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置 + // 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了 + let port_same = cur.map_or(true, |cur| old.port == cur.port); + + if old.enable && port_same { + old.enable = false; + log::info!(target: "app", "reset proxy by disabling the original proxy"); + } else { + log::info!(target: "app", "reset proxy to the original proxy"); + } + + old.set_system_proxy()?; + } else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur { + // 没有原代理,就按现在的代理设置disable即可 + log::info!(target: "app", "reset proxy by disabling the current proxy"); + cur.enable = false; + cur.set_system_proxy()?; + } else { + log::info!(target: "app", "reset proxy with no action"); + } + + Ok(()) + } + + /// init the auto launch + pub fn init_launch(&mut self) -> Result<()> { + let data = Data::global(); + let verge = data.verge.lock(); + let enable = verge.enable_auto_launch.clone().unwrap_or(false); + + let app_exe = current_exe()?; + let app_exe = dunce::canonicalize(app_exe)?; + let app_name = app_exe + .file_stem() + .and_then(|f| f.to_str()) + .ok_or(anyhow!("failed to get file stem"))?; + + let app_path = app_exe + .as_os_str() + .to_str() + .ok_or(anyhow!("failed to get app_path"))? + .to_string(); + + // fix issue #26 + #[cfg(target_os = "windows")] + let app_path = format!("\"{app_path}\""); + + // use the /Applications/Clash Verge.app path + #[cfg(target_os = "macos")] + let app_path = (|| -> Option { + let path = std::path::PathBuf::from(&app_path); + let path = path.parent()?.parent()?.parent()?; + let extension = path.extension()?.to_str()?; + match extension == "app" { + true => Some(path.as_os_str().to_str()?.to_string()), + false => None, + } + })() + .unwrap_or(app_path); + + let auto = AutoLaunchBuilder::new() + .set_app_name(app_name) + .set_app_path(&app_path) + .build()?; + + self.auto_launch = Some(auto); + + // 避免在开发时将自启动关了 + #[cfg(feature = "verge-dev")] + if !enable { + return Ok(()); + } + + let auto = self.auto_launch.as_ref().unwrap(); + + // macos每次启动都更新登录项,避免重复设置登录项 + #[cfg(target_os = "macos")] + { + let _ = auto.disable(); + if enable { + auto.enable()?; + } + } + + #[cfg(not(target_os = "macos"))] + { + match enable { + true => auto.enable()?, + false => auto.disable()?, }; - - log_if_err!(sysproxy.set_system_proxy()); - } - Err(_) => log::error!(target: "app", "failed to parse clash port"), } - } - let mut state = guard_state.lock().await; - *state = false; - }); - } + Ok(()) + } + + /// update the startup + pub fn update_launch(&mut self) -> Result<()> { + if self.auto_launch.is_none() { + return self.init_launch(); + } + + let data = Data::global(); + let verge = data.verge.lock(); + let enable = verge.enable_auto_launch.clone().unwrap_or(false); + + let auto_launch = self.auto_launch.as_ref().unwrap(); + + match enable { + true => auto_launch.enable()?, + false => crate::log_if_err!(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; + + let global = Data::global(); + let verge = global.verge.lock(); + + let enable = verge.enable_system_proxy.clone().unwrap_or(false); + let guard = verge.enable_proxy_guard.clone().unwrap_or(false); + let guard_duration = verge.proxy_guard_duration.clone().unwrap_or(10); + let bypass = verge.system_proxy_bypass.clone(); + drop(verge); + + // stop loop + if !enable || !guard { + break; + } + + // update duration + wait_secs = guard_duration; + + let clash = global.clash.lock(); + let port = clash.info.port.clone(); + let port = port.unwrap_or("".into()).parse::(); + drop(clash); + + log::debug!(target: "app", "try to guard the system proxy"); + + match port { + Ok(port) => { + let sysproxy = Sysproxy { + enable: true, + host: "127.0.0.1".into(), + port, + bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), + }; + + log_if_err!(sysproxy.set_system_proxy()); + } + Err(_) => log::error!(target: "app", "failed to parse clash port"), + } + } + + let mut state = guard_state.lock().await; + *state = false; + }); + } } diff --git a/src-tauri/src/core/timer.rs b/src-tauri/src/core/timer.rs index 3728b76..4d9ccba 100644 --- a/src-tauri/src/core/timer.rs +++ b/src-tauri/src/core/timer.rs @@ -8,165 +8,165 @@ use std::collections::HashMap; type TaskID = u64; pub struct Timer { - /// cron manager - delay_timer: DelayTimer, + /// cron manager + delay_timer: DelayTimer, - /// save the current state - timer_map: HashMap, + /// save the current state + timer_map: HashMap, - /// increment id - timer_count: TaskID, + /// increment id + timer_count: TaskID, } impl Timer { - pub fn new() -> Self { - Timer { - delay_timer: DelayTimerBuilder::default().build(), - timer_map: HashMap::new(), - timer_count: 1, - } - } - - /// Correctly update all cron tasks - pub fn refresh(&mut self) -> Result<()> { - let diff_map = self.gen_diff(); - - for (uid, diff) in diff_map.into_iter() { - match diff { - DiffFlag::Del(tid) => { - let _ = self.timer_map.remove(&uid); - log_if_err!(self.delay_timer.remove_task(tid)); + pub fn new() -> Self { + Timer { + delay_timer: DelayTimerBuilder::default().build(), + timer_map: HashMap::new(), + timer_count: 1, } - DiffFlag::Add(tid, val) => { - let _ = self.timer_map.insert(uid.clone(), (tid, val)); - log_if_err!(self.add_task(uid, tid, val)); - } - DiffFlag::Mod(tid, val) => { - let _ = self.timer_map.insert(uid.clone(), (tid, val)); - log_if_err!(self.delay_timer.remove_task(tid)); - log_if_err!(self.add_task(uid, tid, val)); - } - } } - Ok(()) - } + /// Correctly update all cron tasks + pub fn refresh(&mut self) -> Result<()> { + let diff_map = self.gen_diff(); - /// restore timer - pub fn restore(&mut self) -> Result<()> { - self.refresh()?; - - let cur_timestamp = get_now(); // seconds - - let global = Data::global(); - let profiles = global.profiles.lock(); - - profiles - .get_items() - .unwrap_or(&vec![]) - .iter() - .filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some()) - .filter(|item| { - // mins to seconds - let interval = item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60; - let updated = item.updated.unwrap(); - return interval > 0 && cur_timestamp - updated >= interval; - }) - .for_each(|item| { - let uid = item.uid.as_ref().unwrap(); - if let Some((task_id, _)) = self.timer_map.get(uid) { - log_if_err!(self.delay_timer.advance_task(*task_id)); + for (uid, diff) in diff_map.into_iter() { + match diff { + DiffFlag::Del(tid) => { + let _ = self.timer_map.remove(&uid); + log_if_err!(self.delay_timer.remove_task(tid)); + } + DiffFlag::Add(tid, val) => { + let _ = self.timer_map.insert(uid.clone(), (tid, val)); + log_if_err!(self.add_task(uid, tid, val)); + } + DiffFlag::Mod(tid, val) => { + let _ = self.timer_map.insert(uid.clone(), (tid, val)); + log_if_err!(self.delay_timer.remove_task(tid)); + log_if_err!(self.add_task(uid, tid, val)); + } + } } - }); - Ok(()) - } - - /// generate a uid -> update_interval map - fn gen_map(&self) -> HashMap { - let global = Data::global(); - let profiles = global.profiles.lock(); - - let mut new_map = HashMap::new(); - - if let Some(items) = profiles.get_items() { - for item in items.iter() { - if item.option.is_some() { - let option = item.option.as_ref().unwrap(); - let interval = option.update_interval.unwrap_or(0); - - if interval > 0 { - new_map.insert(item.uid.clone().unwrap(), interval); - } - } - } + Ok(()) } - new_map - } + /// restore timer + pub fn restore(&mut self) -> Result<()> { + self.refresh()?; - /// generate the diff map for refresh - fn gen_diff(&mut self) -> HashMap { - let mut diff_map = HashMap::new(); + let cur_timestamp = get_now(); // seconds - let new_map = self.gen_map(); - let cur_map = &self.timer_map; + let global = Data::global(); + let profiles = global.profiles.lock(); - cur_map.iter().for_each(|(uid, (tid, val))| { - let new_val = new_map.get(uid).unwrap_or(&0); + profiles + .get_items() + .unwrap_or(&vec![]) + .iter() + .filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some()) + .filter(|item| { + // mins to seconds + let interval = + item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60; + let updated = item.updated.unwrap(); + return interval > 0 && cur_timestamp - updated >= interval; + }) + .for_each(|item| { + let uid = item.uid.as_ref().unwrap(); + if let Some((task_id, _)) = self.timer_map.get(uid) { + log_if_err!(self.delay_timer.advance_task(*task_id)); + } + }); - if *new_val == 0 { - diff_map.insert(uid.clone(), DiffFlag::Del(*tid)); - } else if new_val != val { - diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val)); - } - }); + Ok(()) + } - let mut count = self.timer_count; + /// generate a uid -> update_interval map + fn gen_map(&self) -> HashMap { + let global = Data::global(); + let profiles = global.profiles.lock(); - new_map.iter().for_each(|(uid, val)| { - if cur_map.get(uid).is_none() { - diff_map.insert(uid.clone(), DiffFlag::Add(count, *val)); + let mut new_map = HashMap::new(); - count += 1; - } - }); + if let Some(items) = profiles.get_items() { + for item in items.iter() { + if item.option.is_some() { + let option = item.option.as_ref().unwrap(); + let interval = option.update_interval.unwrap_or(0); - self.timer_count = count; + if interval > 0 { + new_map.insert(item.uid.clone().unwrap(), interval); + } + } + } + } - diff_map - } + new_map + } - /// add a cron task - fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> { - let core = Core::global(); + /// generate the diff map for refresh + fn gen_diff(&mut self) -> HashMap { + let mut diff_map = HashMap::new(); - let task = TaskBuilder::default() - .set_task_id(tid) - .set_maximum_parallel_runnable_num(1) - .set_frequency_repeated_by_minutes(minutes) - // .set_frequency_repeated_by_seconds(minutes) // for test - .spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned())) - .context("failed to create timer task")?; + let new_map = self.gen_map(); + let cur_map = &self.timer_map; - self - .delay_timer - .add_task(task) - .context("failed to add timer task")?; + cur_map.iter().for_each(|(uid, (tid, val))| { + let new_val = new_map.get(uid).unwrap_or(&0); - Ok(()) - } + if *new_val == 0 { + diff_map.insert(uid.clone(), DiffFlag::Del(*tid)); + } else if new_val != val { + diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val)); + } + }); - /// the task runner - async fn async_task(core: Core, uid: String) { - log::info!(target: "app", "running timer task `{uid}`"); - log_if_err!(core.update_profile_item(uid, None).await); - } + let mut count = self.timer_count; + + new_map.iter().for_each(|(uid, val)| { + if cur_map.get(uid).is_none() { + diff_map.insert(uid.clone(), DiffFlag::Add(count, *val)); + + count += 1; + } + }); + + self.timer_count = count; + + diff_map + } + + /// add a cron task + fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> { + let core = Core::global(); + + let task = TaskBuilder::default() + .set_task_id(tid) + .set_maximum_parallel_runnable_num(1) + .set_frequency_repeated_by_minutes(minutes) + // .set_frequency_repeated_by_seconds(minutes) // for test + .spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned())) + .context("failed to create timer task")?; + + self.delay_timer + .add_task(task) + .context("failed to add timer task")?; + + Ok(()) + } + + /// the task runner + async fn async_task(core: Core, uid: String) { + log::info!(target: "app", "running timer task `{uid}`"); + log_if_err!(core.update_profile_item(uid, None).await); + } } #[derive(Debug)] enum DiffFlag { - Del(TaskID), - Add(TaskID, u64), - Mod(TaskID, u64), + Del(TaskID), + Add(TaskID, u64), + Mod(TaskID, u64), } diff --git a/src-tauri/src/core/tray.rs b/src-tauri/src/core/tray.rs index 6b3fb99..a65c457 100644 --- a/src-tauri/src/core/tray.rs +++ b/src-tauri/src/core/tray.rs @@ -1,124 +1,130 @@ use crate::{data::Data, feat, utils::resolve}; use anyhow::{Ok, Result}; use tauri::{ - api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, - SystemTraySubmenu, + api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, + SystemTraySubmenu, }; pub struct Tray {} impl Tray { - pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu { - let data = Data::global(); - let zh = { - let verge = data.verge.lock(); - verge.language == Some("zh".into()) - }; + pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu { + let data = Data::global(); + let zh = { + let verge = data.verge.lock(); + verge.language == Some("zh".into()) + }; - let version = app_handle.package_info().version.to_string(); + let version = app_handle.package_info().version.to_string(); - if zh { - SystemTrayMenu::new() - .add_item(CustomMenuItem::new("open_window", "打开面板")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("rule_mode", "规则模式")) - .add_item(CustomMenuItem::new("global_mode", "全局模式")) - .add_item(CustomMenuItem::new("direct_mode", "直连模式")) - .add_item(CustomMenuItem::new("script_mode", "脚本模式")) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("system_proxy", "系统代理")) - .add_item(CustomMenuItem::new("tun_mode", "TUN 模式")) - .add_submenu(SystemTraySubmenu::new( - "更多", - SystemTrayMenu::new() - .add_item(CustomMenuItem::new("restart_clash", "重启 Clash")) - .add_item(CustomMenuItem::new("restart_app", "重启应用")) - .add_item(CustomMenuItem::new("app_version", format!("Version {version}")).disabled()), - )) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit", "退出").accelerator("CmdOrControl+Q")) - } else { - SystemTrayMenu::new() - .add_item(CustomMenuItem::new("open_window", "Dashboard")) - .add_native_item(SystemTrayMenuItem::Separator) - .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_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("system_proxy", "System Proxy")) - .add_item(CustomMenuItem::new("tun_mode", "Tun Mode")) - .add_submenu(SystemTraySubmenu::new( - "More", - SystemTrayMenu::new() - .add_item(CustomMenuItem::new("restart_clash", "Restart Clash")) - .add_item(CustomMenuItem::new("restart_app", "Restart App")) - .add_item(CustomMenuItem::new("app_version", format!("Version {version}")).disabled()), - )) - .add_native_item(SystemTrayMenuItem::Separator) - .add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q")) - } - } - - pub fn update_systray(app_handle: &AppHandle) -> Result<()> { - app_handle - .tray_handle() - .set_menu(Tray::tray_menu(app_handle))?; - Tray::update_part(app_handle)?; - Ok(()) - } - - pub fn update_part(app_handle: &AppHandle) -> Result<()> { - let global = Data::global(); - let clash = global.clash.lock(); - let mode = clash - .config - .get(&serde_yaml::Value::from("mode")) - .map(|val| val.as_str().unwrap_or("rule")) - .unwrap_or("rule"); - - let tray = app_handle.tray_handle(); - - let _ = tray.get_item("rule_mode").set_selected(mode == "rule"); - let _ = tray.get_item("global_mode").set_selected(mode == "global"); - let _ = tray.get_item("direct_mode").set_selected(mode == "direct"); - let _ = tray.get_item("script_mode").set_selected(mode == "script"); - - let verge = global.verge.lock(); - let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); - let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); - - let _ = tray.get_item("system_proxy").set_selected(*system_proxy); - let _ = tray.get_item("tun_mode").set_selected(*tun_mode); - - Ok(()) - } - - pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) { - match event { - SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { - mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => { - let mode = &mode[0..mode.len() - 5]; - feat::change_clash_mode(mode); + if zh { + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("open_window", "打开面板")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("rule_mode", "规则模式")) + .add_item(CustomMenuItem::new("global_mode", "全局模式")) + .add_item(CustomMenuItem::new("direct_mode", "直连模式")) + .add_item(CustomMenuItem::new("script_mode", "脚本模式")) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("system_proxy", "系统代理")) + .add_item(CustomMenuItem::new("tun_mode", "TUN 模式")) + .add_submenu(SystemTraySubmenu::new( + "更多", + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("restart_clash", "重启 Clash")) + .add_item(CustomMenuItem::new("restart_app", "重启应用")) + .add_item( + CustomMenuItem::new("app_version", format!("Version {version}")) + .disabled(), + ), + )) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit", "退出").accelerator("CmdOrControl+Q")) + } else { + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("open_window", "Dashboard")) + .add_native_item(SystemTrayMenuItem::Separator) + .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_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("system_proxy", "System Proxy")) + .add_item(CustomMenuItem::new("tun_mode", "Tun Mode")) + .add_submenu(SystemTraySubmenu::new( + "More", + SystemTrayMenu::new() + .add_item(CustomMenuItem::new("restart_clash", "Restart Clash")) + .add_item(CustomMenuItem::new("restart_app", "Restart App")) + .add_item( + CustomMenuItem::new("app_version", format!("Version {version}")) + .disabled(), + ), + )) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q")) + } + } + + pub fn update_systray(app_handle: &AppHandle) -> Result<()> { + app_handle + .tray_handle() + .set_menu(Tray::tray_menu(app_handle))?; + Tray::update_part(app_handle)?; + Ok(()) + } + + pub fn update_part(app_handle: &AppHandle) -> Result<()> { + let global = Data::global(); + let clash = global.clash.lock(); + let mode = clash + .config + .get(&serde_yaml::Value::from("mode")) + .map(|val| val.as_str().unwrap_or("rule")) + .unwrap_or("rule"); + + let tray = app_handle.tray_handle(); + + let _ = tray.get_item("rule_mode").set_selected(mode == "rule"); + let _ = tray.get_item("global_mode").set_selected(mode == "global"); + let _ = tray.get_item("direct_mode").set_selected(mode == "direct"); + let _ = tray.get_item("script_mode").set_selected(mode == "script"); + + let verge = global.verge.lock(); + let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); + let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); + + let _ = tray.get_item("system_proxy").set_selected(*system_proxy); + let _ = tray.get_item("tun_mode").set_selected(*tun_mode); + + Ok(()) + } + + pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) { + match event { + SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { + mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => { + let mode = &mode[0..mode.len() - 5]; + feat::change_clash_mode(mode); + } + + "open_window" => resolve::create_window(app_handle), + "system_proxy" => feat::toggle_system_proxy(), + "tun_mode" => feat::toggle_tun_mode(), + "restart_clash" => feat::restart_clash_core(), + "restart_app" => api::process::restart(&app_handle.env()), + "quit" => { + resolve::resolve_reset(); + api::process::kill_children(); + app_handle.exit(0); + } + _ => {} + }, + #[cfg(target_os = "windows")] + SystemTrayEvent::LeftClick { .. } => { + resolve::create_window(app_handle); + } + _ => {} } - - "open_window" => resolve::create_window(app_handle), - "system_proxy" => feat::toggle_system_proxy(), - "tun_mode" => feat::toggle_tun_mode(), - "restart_clash" => feat::restart_clash_core(), - "restart_app" => api::process::restart(&app_handle.env()), - "quit" => { - resolve::resolve_reset(); - api::process::kill_children(); - app_handle.exit(0); - } - _ => {} - }, - #[cfg(target_os = "windows")] - SystemTrayEvent::LeftClick { .. } => { - resolve::create_window(app_handle); - } - _ => {} } - } } diff --git a/src-tauri/src/data/clash.rs b/src-tauri/src/data/clash.rs index 56bbff3..73ecd35 100644 --- a/src-tauri/src/data/clash.rs +++ b/src-tauri/src/data/clash.rs @@ -5,163 +5,163 @@ 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, } impl ClashInfo { - /// parse the clash's config.yaml - /// get some information - pub fn from(config: &Mapping) -> ClashInfo { - let key_port_1 = Value::from("mixed-port"); - let key_port_2 = Value::from("port"); - let key_server = Value::from("external-controller"); - let key_secret = Value::from("secret"); + /// parse the clash's config.yaml + /// get some information + pub fn from(config: &Mapping) -> ClashInfo { + let key_port_1 = Value::from("mixed-port"); + let key_port_2 = Value::from("port"); + let key_server = Value::from("external-controller"); + let key_secret = Value::from("secret"); - let mut status: u32 = 0; + let mut status: u32 = 0; - 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()), - _ => { - status |= 0b1; - 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()), + _ => { + status |= 0b1; + None + } + }, + _ => { + status |= 0b10; + 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()), + _ => { + status |= 0b100; + None + } + }, + _ => { + status |= 0b1000; + None + } + }, + }; + + // `external-controller` could be + // "127.0.0.1:9090" or ":9090" + let server = match config.get(&key_server) { + Some(value) => match value.as_str() { + Some(val_str) => { + if val_str.starts_with(":") { + Some(format!("127.0.0.1{val_str}")) + } else if val_str.starts_with("0.0.0.0:") { + Some(format!("127.0.0.1:{}", &val_str[8..])) + } else if val_str.starts_with("[::]:") { + Some(format!("127.0.0.1:{}", &val_str[5..])) + } else { + Some(val_str.into()) + } + } + None => { + status |= 0b10000; + None + } + }, + None => { + status |= 0b100000; + 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, + }; + + ClashInfo { + status: format!("{status}"), + port, + server, + secret, } - }, - _ => { - status |= 0b10; - 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()), - _ => { - status |= 0b100; - None - } - }, - _ => { - status |= 0b1000; - None - } - }, - }; - - // `external-controller` could be - // "127.0.0.1:9090" or ":9090" - let server = match config.get(&key_server) { - Some(value) => match value.as_str() { - Some(val_str) => { - if val_str.starts_with(":") { - Some(format!("127.0.0.1{val_str}")) - } else if val_str.starts_with("0.0.0.0:") { - Some(format!("127.0.0.1:{}", &val_str[8..])) - } else if val_str.starts_with("[::]:") { - Some(format!("127.0.0.1:{}", &val_str[5..])) - } else { - Some(val_str.into()) - } - } - None => { - status |= 0b10000; - None - } - }, - None => { - status |= 0b100000; - 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, - }; - - ClashInfo { - status: format!("{status}"), - port, - server, - secret, } - } } #[derive(Debug)] 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 } - } - - /// get clash config - pub fn read_config() -> Mapping { - config::read_merge_mapping(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<()> { - let port_key = Value::from("mixed-port"); - let server_key = Value::from("external-controller"); - let secret_key = Value::from("secret"); - - let change_info = patch.contains_key(&port_key) - || patch.contains_key(&server_key) - || patch.contains_key(&secret_key); - - for (key, value) in patch.into_iter() { - self.config.insert(key, value); + Clash { config, info } } - if change_info { - self.info = ClashInfo::from(&self.config); + /// get clash config + pub fn read_config() -> Mapping { + config::read_merge_mapping(dirs::clash_path()) } - self.save_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"), + ) + } + + /// patch update the clash config + /// if the port is changed then return true + pub fn patch_config(&mut self, patch: Mapping) -> Result<()> { + let port_key = Value::from("mixed-port"); + let server_key = Value::from("external-controller"); + let secret_key = Value::from("secret"); + + let change_info = patch.contains_key(&port_key) + || patch.contains_key(&server_key) + || patch.contains_key(&secret_key); + + for (key, value) in patch.into_iter() { + self.config.insert(key, value); + } + + if change_info { + self.info = ClashInfo::from(&self.config); + } + + self.save_config() + } } impl Default for Clash { - fn default() -> Self { - Clash::new() - } + fn default() -> Self { + Clash::new() + } } diff --git a/src-tauri/src/data/mod.rs b/src-tauri/src/data/mod.rs index 3ba6781..eb6a406 100644 --- a/src-tauri/src/data/mod.rs +++ b/src-tauri/src/data/mod.rs @@ -14,19 +14,19 @@ use std::sync::Arc; #[derive(Debug, Clone)] pub struct Data { - pub clash: Arc>, - pub verge: Arc>, - pub profiles: Arc>, + pub clash: Arc>, + pub verge: Arc>, + pub profiles: Arc>, } impl Data { - pub fn global() -> &'static Data { - static DATA: OnceCell = OnceCell::new(); + pub fn global() -> &'static Data { + static DATA: OnceCell = OnceCell::new(); - DATA.get_or_init(|| Data { - clash: Arc::new(Mutex::new(Clash::new())), - verge: Arc::new(Mutex::new(Verge::new())), - profiles: Arc::new(Mutex::new(Profiles::new())), - }) - } + DATA.get_or_init(|| Data { + clash: Arc::new(Mutex::new(Clash::new())), + verge: Arc::new(Mutex::new(Verge::new())), + profiles: Arc::new(Mutex::new(Profiles::new())), + }) + } } diff --git a/src-tauri/src/data/prfitem.rs b/src-tauri/src/data/prfitem.rs index ea2dd11..90b08f3 100644 --- a/src-tauri/src/data/prfitem.rs +++ b/src-tauri/src/data/prfitem.rs @@ -8,399 +8,399 @@ use sysproxy::Sysproxy; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PrfItem { - pub uid: Option, + pub uid: Option, - /// profile item type - /// enum value: remote | local | script | merge - #[serde(rename = "type")] - pub itype: Option, + /// profile item type + /// enum value: remote | local | script | merge + #[serde(rename = "type")] + pub itype: Option, - /// profile name - pub name: Option, + /// profile name + pub name: Option, - /// profile file - pub file: Option, + /// profile file + pub file: Option, - /// profile description - #[serde(skip_serializing_if = "Option::is_none")] - pub desc: Option, + /// profile description + #[serde(skip_serializing_if = "Option::is_none")] + pub desc: Option, - /// source url - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, + /// source url + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, - /// selected infomation - #[serde(skip_serializing_if = "Option::is_none")] - pub selected: Option>, + /// selected infomation + #[serde(skip_serializing_if = "Option::is_none")] + pub selected: Option>, - /// subscription user info - #[serde(skip_serializing_if = "Option::is_none")] - pub extra: Option, + /// subscription user info + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option, - /// updated time - pub updated: Option, + /// updated time + pub updated: Option, - /// some options of the item - #[serde(skip_serializing_if = "Option::is_none")] - pub option: Option, + /// some options of the item + #[serde(skip_serializing_if = "Option::is_none")] + pub option: Option, - /// the file data - #[serde(skip)] - pub file_data: Option, + /// the file data + #[serde(skip)] + pub file_data: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct PrfSelected { - pub name: Option, - pub now: Option, + pub name: Option, + pub now: Option, } #[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)] pub struct PrfExtra { - pub upload: usize, - pub download: usize, - pub total: usize, - pub expire: usize, + pub upload: usize, + pub download: usize, + pub total: usize, + pub expire: usize, } #[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct PrfOption { - /// for `remote` profile's http request - /// see issue #13 - #[serde(skip_serializing_if = "Option::is_none")] - pub user_agent: Option, + /// for `remote` profile's http request + /// see issue #13 + #[serde(skip_serializing_if = "Option::is_none")] + pub user_agent: Option, - /// for `remote` profile - /// use system proxy - #[serde(skip_serializing_if = "Option::is_none")] - pub with_proxy: Option, + /// for `remote` profile + /// use system proxy + #[serde(skip_serializing_if = "Option::is_none")] + pub with_proxy: Option, - /// for `remote` profile - /// use self proxy - #[serde(skip_serializing_if = "Option::is_none")] - pub self_proxy: Option, + /// for `remote` profile + /// use self proxy + #[serde(skip_serializing_if = "Option::is_none")] + pub self_proxy: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub update_interval: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub update_interval: Option, } impl PrfOption { - pub fn merge(one: Option, other: Option) -> Option { - match (one, other) { - (Some(mut a), Some(b)) => { - a.user_agent = b.user_agent.or(a.user_agent); - a.with_proxy = b.with_proxy.or(a.with_proxy); - a.self_proxy = b.self_proxy.or(a.self_proxy); - a.update_interval = b.update_interval.or(a.update_interval); - Some(a) - } - t @ _ => t.0.or(t.1), + pub fn merge(one: Option, other: Option) -> Option { + match (one, other) { + (Some(mut a), Some(b)) => { + a.user_agent = b.user_agent.or(a.user_agent); + a.with_proxy = b.with_proxy.or(a.with_proxy); + a.self_proxy = b.self_proxy.or(a.self_proxy); + a.update_interval = b.update_interval.or(a.update_interval); + Some(a) + } + t @ _ => t.0.or(t.1), + } } - } } impl Default for PrfItem { - fn default() -> Self { - PrfItem { - uid: None, - itype: None, - name: None, - desc: None, - file: None, - url: None, - selected: None, - extra: None, - updated: None, - option: None, - file_data: None, + fn default() -> Self { + PrfItem { + uid: None, + itype: None, + name: None, + desc: None, + file: None, + url: None, + selected: None, + extra: None, + updated: None, + option: None, + file_data: None, + } } - } } impl PrfItem { - /// From partial item - /// must contain `itype` - pub async fn from(item: PrfItem, file_data: Option) -> Result { - if item.itype.is_none() { - bail!("type should not be null"); - } - - match item.itype.unwrap().as_str() { - "remote" => { - if item.url.is_none() { - bail!("url should not be null"); + /// From partial item + /// must contain `itype` + pub async fn from(item: PrfItem, file_data: Option) -> Result { + if item.itype.is_none() { + bail!("type should not be null"); } - let url = item.url.as_ref().unwrap().as_str(); - let name = item.name; - let desc = item.desc; - PrfItem::from_url(url, name, desc, item.option).await - } - "local" => { - let name = item.name.unwrap_or("Local File".into()); - let desc = item.desc.unwrap_or("".into()); - PrfItem::from_local(name, desc, file_data) - } - "merge" => { - let name = item.name.unwrap_or("Merge".into()); - let desc = item.desc.unwrap_or("".into()); - PrfItem::from_merge(name, desc) - } - "script" => { - let name = item.name.unwrap_or("Script".into()); - let desc = item.desc.unwrap_or("".into()); - PrfItem::from_script(name, desc) - } - typ @ _ => bail!("invalid profile item type \"{typ}\""), - } - } - /// ## Local type - /// create a new item from name/desc - pub fn from_local(name: String, desc: String, file_data: Option) -> Result { - let uid = help::get_uid("l"); - let file = format!("{uid}.yaml"); - - Ok(PrfItem { - uid: Some(uid), - itype: Some("local".into()), - name: Some(name), - desc: Some(desc), - file: Some(file), - url: None, - selected: None, - extra: None, - option: None, - updated: Some(help::get_now()), - file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())), - }) - } - - /// ## Remote type - /// create a new item from url - pub async fn from_url( - url: &str, - name: Option, - desc: Option, - option: Option, - ) -> Result { - let opt_ref = option.as_ref(); - let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false)); - let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false)); - let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone()); - - let mut builder = reqwest::ClientBuilder::new().no_proxy(); - - // 使用软件自己的代理 - if self_proxy { - let data = super::Data::global(); - let port = data.clash.lock().info.port.clone(); - let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?; - let proxy_scheme = format!("http://127.0.0.1:{port}"); - - if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { - builder = builder.proxy(proxy); - } - } - // 使用系统代理 - else if with_proxy { - match Sysproxy::get_system_proxy() { - Ok(p @ Sysproxy { enable: true, .. }) => { - let proxy_scheme = format!("http://{}:{}", p.host, p.port); - - if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { - builder = builder.proxy(proxy); - } + match item.itype.unwrap().as_str() { + "remote" => { + if item.url.is_none() { + bail!("url should not be null"); + } + let url = item.url.as_ref().unwrap().as_str(); + let name = item.name; + let desc = item.desc; + PrfItem::from_url(url, name, desc, item.option).await + } + "local" => { + let name = item.name.unwrap_or("Local File".into()); + let desc = item.desc.unwrap_or("".into()); + PrfItem::from_local(name, desc, file_data) + } + "merge" => { + let name = item.name.unwrap_or("Merge".into()); + let desc = item.desc.unwrap_or("".into()); + PrfItem::from_merge(name, desc) + } + "script" => { + let name = item.name.unwrap_or("Script".into()); + let desc = item.desc.unwrap_or("".into()); + PrfItem::from_script(name, desc) + } + typ @ _ => bail!("invalid profile item type \"{typ}\""), } - _ => {} - }; } - let version = unsafe { dirs::APP_VERSION }; - let version = format!("clash-verge/{version}"); - builder = builder.user_agent(user_agent.unwrap_or(version)); + /// ## Local type + /// create a new item from name/desc + pub fn from_local(name: String, desc: String, file_data: Option) -> Result { + let uid = help::get_uid("l"); + let file = format!("{uid}.yaml"); - let resp = builder.build()?.get(url).send().await?; - - let status_code = resp.status(); - if !StatusCode::is_success(&status_code) { - bail!("failed to fetch remote profile with status {status_code}") - } - - let header = resp.headers(); - - // parse the Subscription UserInfo - let extra = match header.get("Subscription-Userinfo") { - Some(value) => { - let sub_info = value.to_str().unwrap_or(""); - - Some(PrfExtra { - upload: help::parse_str(sub_info, "upload=").unwrap_or(0), - download: help::parse_str(sub_info, "download=").unwrap_or(0), - total: help::parse_str(sub_info, "total=").unwrap_or(0), - expire: help::parse_str(sub_info, "expire=").unwrap_or(0), + Ok(PrfItem { + uid: Some(uid), + itype: Some("local".into()), + name: Some(name), + desc: Some(desc), + file: Some(file), + url: None, + selected: None, + extra: None, + option: None, + updated: Some(help::get_now()), + file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())), }) - } - None => None, - }; - - // parse the Content-Disposition - let filename = match header.get("Content-Disposition") { - Some(value) => { - let filename = value.to_str().unwrap_or(""); - help::parse_str::(filename, "filename=") - } - None => None, - }; - - // parse the profile-update-interval - let option = match header.get("profile-update-interval") { - Some(value) => match value.to_str().unwrap_or("").parse::() { - Ok(val) => Some(PrfOption { - update_interval: Some(val * 60), // hour -> min - ..PrfOption::default() - }), - Err(_) => None, - }, - None => None, - }; - - let uid = help::get_uid("r"); - let file = format!("{uid}.yaml"); - let name = name.unwrap_or(filename.unwrap_or("Remote File".into())); - let data = resp.text_with_charset("utf-8").await?; - - // check the data whether the valid yaml format - let yaml = serde_yaml::from_str::(&data) // - .context("the remote profile data is invalid yaml")?; - - if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") { - bail!("profile does not contain `proxies` or `proxy-providers`"); } - Ok(PrfItem { - uid: Some(uid), - itype: Some("remote".into()), - name: Some(name), - desc, - file: Some(file), - url: Some(url.into()), - selected: None, - extra, - option, - updated: Some(help::get_now()), - file_data: Some(data), - }) - } + /// ## Remote type + /// create a new item from url + pub async fn from_url( + url: &str, + name: Option, + desc: Option, + option: Option, + ) -> Result { + let opt_ref = option.as_ref(); + let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false)); + let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false)); + let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone()); - /// ## Merge type (enhance) - /// create the enhanced item by using `merge` rule - pub fn from_merge(name: String, desc: String) -> Result { - let uid = help::get_uid("m"); - let file = format!("{uid}.yaml"); + let mut builder = reqwest::ClientBuilder::new().no_proxy(); - Ok(PrfItem { - uid: Some(uid), - itype: Some("merge".into()), - name: Some(name), - desc: Some(desc), - file: Some(file), - url: None, - selected: None, - extra: None, - option: None, - updated: Some(help::get_now()), - file_data: Some(tmpl::ITEM_MERGE.into()), - }) - } + // 使用软件自己的代理 + if self_proxy { + let data = super::Data::global(); + let port = data.clash.lock().info.port.clone(); + let port = port.ok_or(anyhow::anyhow!("failed to get clash info port"))?; + let proxy_scheme = format!("http://127.0.0.1:{port}"); - /// ## Script type (enhance) - /// create the enhanced item by using javascript(browserjs) - pub fn from_script(name: String, desc: String) -> Result { - let uid = help::get_uid("s"); - let file = format!("{uid}.js"); // js ext + if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { + builder = builder.proxy(proxy); + } + } + // 使用系统代理 + else if with_proxy { + match Sysproxy::get_system_proxy() { + Ok(p @ Sysproxy { enable: true, .. }) => { + let proxy_scheme = format!("http://{}:{}", p.host, p.port); - Ok(PrfItem { - uid: Some(uid), - itype: Some("script".into()), - name: Some(name), - desc: Some(desc), - file: Some(file), - url: None, - selected: None, - extra: None, - option: None, - updated: Some(help::get_now()), - file_data: Some(tmpl::ITEM_SCRIPT.into()), - }) - } + if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { + builder = builder.proxy(proxy); + } + } + _ => {} + }; + } - /// get the file data - pub fn read_file(&self) -> Result { - if self.file.is_none() { - bail!("could not find the file"); + let version = unsafe { dirs::APP_VERSION }; + let version = format!("clash-verge/{version}"); + builder = builder.user_agent(user_agent.unwrap_or(version)); + + let resp = builder.build()?.get(url).send().await?; + + let status_code = resp.status(); + if !StatusCode::is_success(&status_code) { + bail!("failed to fetch remote profile with status {status_code}") + } + + let header = resp.headers(); + + // parse the Subscription UserInfo + let extra = match header.get("Subscription-Userinfo") { + Some(value) => { + let sub_info = value.to_str().unwrap_or(""); + + Some(PrfExtra { + upload: help::parse_str(sub_info, "upload=").unwrap_or(0), + download: help::parse_str(sub_info, "download=").unwrap_or(0), + total: help::parse_str(sub_info, "total=").unwrap_or(0), + expire: help::parse_str(sub_info, "expire=").unwrap_or(0), + }) + } + None => None, + }; + + // parse the Content-Disposition + let filename = match header.get("Content-Disposition") { + Some(value) => { + let filename = value.to_str().unwrap_or(""); + help::parse_str::(filename, "filename=") + } + None => None, + }; + + // parse the profile-update-interval + let option = match header.get("profile-update-interval") { + Some(value) => match value.to_str().unwrap_or("").parse::() { + Ok(val) => Some(PrfOption { + update_interval: Some(val * 60), // hour -> min + ..PrfOption::default() + }), + Err(_) => None, + }, + None => None, + }; + + let uid = help::get_uid("r"); + let file = format!("{uid}.yaml"); + let name = name.unwrap_or(filename.unwrap_or("Remote File".into())); + let data = resp.text_with_charset("utf-8").await?; + + // check the data whether the valid yaml format + let yaml = serde_yaml::from_str::(&data) // + .context("the remote profile data is invalid yaml")?; + + if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") { + bail!("profile does not contain `proxies` or `proxy-providers`"); + } + + Ok(PrfItem { + uid: Some(uid), + itype: Some("remote".into()), + name: Some(name), + desc, + file: Some(file), + url: Some(url.into()), + selected: None, + extra, + option, + updated: Some(help::get_now()), + file_data: Some(data), + }) } - let file = self.file.clone().unwrap(); - let path = dirs::app_profiles_dir().join(file); - fs::read_to_string(path).context("failed to read the file") - } + /// ## Merge type (enhance) + /// create the enhanced item by using `merge` rule + pub fn from_merge(name: String, desc: String) -> Result { + let uid = help::get_uid("m"); + let file = format!("{uid}.yaml"); - /// save the file data - pub fn save_file(&self, data: String) -> Result<()> { - if self.file.is_none() { - bail!("could not find the file"); + Ok(PrfItem { + uid: Some(uid), + itype: Some("merge".into()), + name: Some(name), + desc: Some(desc), + file: Some(file), + url: None, + selected: None, + extra: None, + option: None, + updated: Some(help::get_now()), + file_data: Some(tmpl::ITEM_MERGE.into()), + }) } - let file = self.file.clone().unwrap(); - let path = dirs::app_profiles_dir().join(file); - fs::write(path, data.as_bytes()).context("failed to save the file") - } + /// ## Script type (enhance) + /// create the enhanced item by using javascript(browserjs) + pub fn from_script(name: String, desc: String) -> Result { + let uid = help::get_uid("s"); + let file = format!("{uid}.js"); // js ext - /// get the data for enhanced mode - pub fn to_enhance(&self) -> Option { - let itype = self.itype.as_ref()?.as_str(); - let file = self.file.clone()?; - let uid = self.uid.clone().unwrap_or("".into()); - let path = dirs::app_profiles_dir().join(file); - - if !path.exists() { - return None; + Ok(PrfItem { + uid: Some(uid), + itype: Some("script".into()), + name: Some(name), + desc: Some(desc), + file: Some(file), + url: None, + selected: None, + extra: None, + option: None, + updated: Some(help::get_now()), + file_data: Some(tmpl::ITEM_SCRIPT.into()), + }) } - match itype { - "script" => Some(ChainItem { - uid, - data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())), - }), - "merge" => Some(ChainItem { - uid, - data: ChainType::Merge(config::read_merge_mapping(path)), - }), - _ => None, + /// get the file data + pub fn read_file(&self) -> Result { + if self.file.is_none() { + bail!("could not find the file"); + } + + let file = self.file.clone().unwrap(); + let path = dirs::app_profiles_dir().join(file); + fs::read_to_string(path).context("failed to read the file") + } + + /// save the file data + pub fn save_file(&self, data: String) -> Result<()> { + if self.file.is_none() { + bail!("could not find the file"); + } + + let file = self.file.clone().unwrap(); + let path = dirs::app_profiles_dir().join(file); + fs::write(path, data.as_bytes()).context("failed to save the file") + } + + /// get the data for enhanced mode + pub fn to_enhance(&self) -> Option { + let itype = self.itype.as_ref()?.as_str(); + let file = self.file.clone()?; + let uid = self.uid.clone().unwrap_or("".into()); + let path = dirs::app_profiles_dir().join(file); + + if !path.exists() { + return None; + } + + match itype { + "script" => Some(ChainItem { + uid, + data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())), + }), + "merge" => Some(ChainItem { + uid, + data: ChainType::Merge(config::read_merge_mapping(path)), + }), + _ => None, + } } - } } #[derive(Debug, Clone)] pub struct ChainItem { - pub uid: String, - pub data: ChainType, + pub uid: String, + pub data: ChainType, } #[derive(Debug, Clone)] pub enum ChainType { - Merge(Mapping), - Script(String), + Merge(Mapping), + Script(String), } diff --git a/src-tauri/src/data/profiles.rs b/src-tauri/src/data/profiles.rs index 09ac557..6272cd8 100644 --- a/src-tauri/src/data/profiles.rs +++ b/src-tauri/src/data/profiles.rs @@ -14,315 +14,316 @@ use std::{fs, io::Write}; /// #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct Profiles { - /// same as PrfConfig.current - current: Option, + /// same as PrfConfig.current + current: Option, - /// same as PrfConfig.chain - chain: Option>, + /// same as PrfConfig.chain + chain: Option>, - /// record valid fields for clash - valid: Option>, + /// record valid fields for clash + valid: Option>, - /// profile list - items: Option>, + /// profile list + items: Option>, } macro_rules! patch { - ($lv: expr, $rv: expr, $key: tt) => { - if ($rv.$key).is_some() { - $lv.$key = $rv.$key; - } - }; + ($lv: expr, $rv: expr, $key: tt) => { + if ($rv.$key).is_some() { + $lv.$key = $rv.$key; + } + }; } impl Profiles { - pub fn new() -> Self { - Profiles::read_file() - } - - /// read the config from the file - pub fn read_file() -> Self { - let mut profiles = config::read_yaml::(dirs::profiles_path()); - - if profiles.items.is_none() { - profiles.items = Some(vec![]); + pub fn new() -> Self { + Profiles::read_file() } - // compatiable with the old old old version - profiles.items.as_mut().map(|items| { - for mut item in items.iter_mut() { + /// read the config from the file + pub fn read_file() -> Self { + let mut profiles = config::read_yaml::(dirs::profiles_path()); + + if profiles.items.is_none() { + 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() { + item.uid = Some(help::get_uid("d")); + } + } + }); + + profiles + } + + /// save the config to the file + pub fn save_file(&self) -> Result<()> { + config::save_yaml( + dirs::profiles_path(), + self, + Some("# Profiles Config for Clash Verge\n\n"), + ) + } + + /// get the current uid + pub fn get_current(&self) -> Option { + self.current.clone() + } + + /// only change the main to the target id + pub fn put_current(&mut self, uid: String) -> Result<()> { + if self.items.is_none() { + self.items = Some(vec![]); + } + + let items = self.items.as_ref().unwrap(); + let some_uid = Some(uid.clone()); + + if items.iter().find(|&each| each.uid == some_uid).is_some() { + self.current = some_uid; + return self.save_file(); + } + + bail!("invalid uid \"{uid}\""); + } + + /// just change the `chain` + pub fn put_chain(&mut self, chain: Option>) -> Result<()> { + self.chain = chain; + self.save_file() + } + + /// just change the `field` + pub fn put_valid(&mut self, valid: Option>) -> Result<()> { + self.valid = valid; + self.save_file() + } + + /// get items ref + pub fn get_items(&self) -> Option<&Vec> { + self.items.as_ref() + } + + /// find the item by the uid + pub fn get_item(&self, uid: &String) -> Result<&PrfItem> { + if self.items.is_some() { + let items = self.items.as_ref().unwrap(); + let some_uid = Some(uid.clone()); + + for each in items.iter() { + if each.uid == some_uid { + return Ok(each); + } + } + } + + bail!("failed to get the profile item \"uid:{uid}\""); + } + + /// append new item + /// if the file_data is some + /// then should save the data to file + pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> { if item.uid.is_none() { - item.uid = Some(help::get_uid("d")); + bail!("the uid should not be null"); } - } - }); - profiles - } - - /// save the config to the file - pub fn save_file(&self) -> Result<()> { - config::save_yaml( - dirs::profiles_path(), - self, - Some("# Profiles Config for Clash Verge\n\n"), - ) - } - - /// get the current uid - pub fn get_current(&self) -> Option { - self.current.clone() - } - - /// only change the main to the target id - pub fn put_current(&mut self, uid: String) -> Result<()> { - if self.items.is_none() { - self.items = Some(vec![]); - } - - let items = self.items.as_ref().unwrap(); - let some_uid = Some(uid.clone()); - - if items.iter().find(|&each| each.uid == some_uid).is_some() { - self.current = some_uid; - return self.save_file(); - } - - bail!("invalid uid \"{uid}\""); - } - - /// just change the `chain` - pub fn put_chain(&mut self, chain: Option>) -> Result<()> { - self.chain = chain; - self.save_file() - } - - /// just change the `field` - pub fn put_valid(&mut self, valid: Option>) -> Result<()> { - self.valid = valid; - self.save_file() - } - - /// get items ref - pub fn get_items(&self) -> Option<&Vec> { - self.items.as_ref() - } - - /// find the item by the uid - pub fn get_item(&self, uid: &String) -> Result<&PrfItem> { - if self.items.is_some() { - let items = self.items.as_ref().unwrap(); - let some_uid = Some(uid.clone()); - - for each in items.iter() { - if each.uid == some_uid { - return Ok(each); - } - } - } - - bail!("failed to get the profile item \"uid:{uid}\""); - } - - /// append new item - /// if the file_data is some - /// then should save the data to file - pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> { - if item.uid.is_none() { - bail!("the uid should not be null"); - } - - // save the file data - // move the field value after save - if let Some(file_data) = item.file_data.take() { - if item.file.is_none() { - bail!("the file should not be null"); - } - - let file = item.file.clone().unwrap(); - let path = dirs::app_profiles_dir().join(&file); - - fs::File::create(path) - .context(format!("failed to create file \"{}\"", file))? - .write(file_data.as_bytes()) - .context(format!("failed to write to file \"{}\"", file))?; - } - - if self.items.is_none() { - self.items = Some(vec![]); - } - - self.items.as_mut().map(|items| items.push(item)); - self.save_file() - } - - /// update the item value - pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> { - let mut items = self.items.take().unwrap_or(vec![]); - - for mut each in items.iter_mut() { - if each.uid == Some(uid.clone()) { - patch!(each, item, itype); - patch!(each, item, name); - patch!(each, item, desc); - patch!(each, item, file); - patch!(each, item, url); - patch!(each, item, selected); - patch!(each, item, extra); - patch!(each, item, updated); - patch!(each, item, option); - - self.items = Some(items); - return self.save_file(); - } - } - - self.items = Some(items); - bail!("failed to find the profile item \"uid:{uid}\"") - } - - /// be used to update the remote item - /// only patch `updated` `extra` `file_data` - pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> { - if self.items.is_none() { - self.items = Some(vec![]); - } - - // find the item - let _ = self.get_item(&uid)?; - - if let Some(items) = self.items.as_mut() { - let some_uid = Some(uid.clone()); - - for mut each in items.iter_mut() { - if each.uid == some_uid { - each.extra = item.extra; - each.updated = item.updated; - - // save the file data - // move the field value after save - if let Some(file_data) = item.file_data.take() { - let file = each.file.take(); - let file = file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid))); - - // the file must exists - each.file = Some(file.clone()); + // save the file data + // move the field value after save + if let Some(file_data) = item.file_data.take() { + if item.file.is_none() { + bail!("the file should not be null"); + } + let file = item.file.clone().unwrap(); let path = dirs::app_profiles_dir().join(&file); fs::File::create(path) - .context(format!("failed to create file \"{}\"", file))? - .write(file_data.as_bytes()) - .context(format!("failed to write to file \"{}\"", file))?; - } - - break; + .context(format!("failed to create file \"{}\"", file))? + .write(file_data.as_bytes()) + .context(format!("failed to write to file \"{}\"", file))?; } - } - } - self.save_file() - } - - /// delete item - /// if delete the current then return true - pub fn delete_item(&mut self, uid: String) -> Result { - let current = self.current.as_ref().unwrap_or(&uid); - let current = current.clone(); - - let mut items = self.items.take().unwrap_or(vec![]); - let mut index = None; - - // get the index - for i in 0..items.len() { - if items[i].uid == Some(uid.clone()) { - index = Some(i); - break; - } - } - - if let Some(index) = index { - items.remove(index).file.map(|file| { - let path = dirs::app_profiles_dir().join(file); - if path.exists() { - let _ = fs::remove_file(path); + if self.items.is_none() { + self.items = Some(vec![]); } - }); + + self.items.as_mut().map(|items| items.push(item)); + self.save_file() } - // delete the original uid - if current == uid { - self.current = match items.len() > 0 { - true => items[0].uid.clone(), - false => None, - }; + /// update the item value + pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> { + let mut items = self.items.take().unwrap_or(vec![]); + + for mut each in items.iter_mut() { + if each.uid == Some(uid.clone()) { + patch!(each, item, itype); + patch!(each, item, name); + patch!(each, item, desc); + patch!(each, item, file); + patch!(each, item, url); + patch!(each, item, selected); + patch!(each, item, extra); + patch!(each, item, updated); + patch!(each, item, option); + + self.items = Some(items); + return self.save_file(); + } + } + + self.items = Some(items); + bail!("failed to find the profile item \"uid:{uid}\"") } - self.items = Some(items); - self.save_file()?; - Ok(current == uid) - } + /// be used to update the remote item + /// only patch `updated` `extra` `file_data` + pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> { + if self.items.is_none() { + self.items = Some(vec![]); + } - /// generate the current Mapping data - fn gen_current(&self) -> Result { - let config = Mapping::new(); + // find the item + let _ = self.get_item(&uid)?; - if self.current.is_none() || self.items.is_none() { - return Ok(config); + if let Some(items) = self.items.as_mut() { + let some_uid = Some(uid.clone()); + + for mut each in items.iter_mut() { + if each.uid == some_uid { + each.extra = item.extra; + each.updated = item.updated; + + // save the file data + // move the field value after save + if let Some(file_data) = item.file_data.take() { + let file = each.file.take(); + let file = + file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid))); + + // the file must exists + each.file = Some(file.clone()); + + let path = dirs::app_profiles_dir().join(&file); + + fs::File::create(path) + .context(format!("failed to create file \"{}\"", file))? + .write(file_data.as_bytes()) + .context(format!("failed to write to file \"{}\"", file))?; + } + + break; + } + } + } + + self.save_file() } - let current = self.current.clone().unwrap(); - for item in self.items.as_ref().unwrap().iter() { - if item.uid == Some(current.clone()) { - let file_path = match item.file.clone() { - Some(file) => dirs::app_profiles_dir().join(file), - None => bail!("failed to get the file field"), + /// delete item + /// if delete the current then return true + pub fn delete_item(&mut self, uid: String) -> Result { + let current = self.current.as_ref().unwrap_or(&uid); + let current = current.clone(); + + let mut items = self.items.take().unwrap_or(vec![]); + let mut index = None; + + // get the index + for i in 0..items.len() { + if items[i].uid == Some(uid.clone()) { + index = Some(i); + break; + } + } + + if let Some(index) = index { + items.remove(index).file.map(|file| { + let path = dirs::app_profiles_dir().join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + + // delete the original uid + if current == uid { + self.current = match items.len() > 0 { + true => items[0].uid.clone(), + false => None, + }; + } + + self.items = Some(items); + self.save_file()?; + Ok(current == uid) + } + + /// generate the current Mapping data + fn gen_current(&self) -> Result { + let config = Mapping::new(); + + if self.current.is_none() || self.items.is_none() { + return Ok(config); + } + + let current = self.current.clone().unwrap(); + for item in self.items.as_ref().unwrap().iter() { + if item.uid == Some(current.clone()) { + let file_path = match item.file.clone() { + Some(file) => dirs::app_profiles_dir().join(file), + None => bail!("failed to get the file field"), + }; + + if !file_path.exists() { + bail!("failed to read the file \"{}\"", file_path.display()); + } + + return Ok(config::read_merge_mapping(file_path.clone())); + } + } + bail!("failed to find current profile \"uid:{current}\""); + } + + /// generate the data for activate clash config + pub fn gen_activate(&self) -> Result { + let current = self.gen_current()?; + let chain = match self.chain.as_ref() { + Some(chain) => chain + .iter() + .filter_map(|uid| self.get_item(uid).ok()) + .filter_map(|item| item.to_enhance()) + .collect::>(), + None => vec![], }; + let valid = self.valid.clone().unwrap_or(vec![]); - if !file_path.exists() { - bail!("failed to read the file \"{}\"", file_path.display()); - } - - return Ok(config::read_merge_mapping(file_path.clone())); - } + Ok(PrfActivate { + current, + chain, + valid, + }) } - bail!("failed to find current profile \"uid:{current}\""); - } - - /// generate the data for activate clash config - pub fn gen_activate(&self) -> Result { - let current = self.gen_current()?; - let chain = match self.chain.as_ref() { - Some(chain) => chain - .iter() - .filter_map(|uid| self.get_item(uid).ok()) - .filter_map(|item| item.to_enhance()) - .collect::>(), - None => vec![], - }; - let valid = self.valid.clone().unwrap_or(vec![]); - - Ok(PrfActivate { - current, - chain, - valid, - }) - } } #[derive(Default, Clone)] pub struct PrfActivate { - pub current: Mapping, - pub chain: Vec, - pub valid: Vec, + pub current: Mapping, + pub chain: Vec, + pub valid: Vec, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct RuntimeResult { - pub config: Option, - pub config_yaml: Option, - // 记录在配置中(包括merge和script生成的)出现过的keys - // 这些keys不一定都生效 - pub exists_keys: Vec, - pub chain_logs: HashMap>, + pub config: Option, + pub config_yaml: Option, + // 记录在配置中(包括merge和script生成的)出现过的keys + // 这些keys不一定都生效 + pub exists_keys: Vec, + pub chain_logs: HashMap>, } diff --git a/src-tauri/src/data/verge.rs b/src-tauri/src/data/verge.rs index 9a5519e..97c2016 100644 --- a/src-tauri/src/data/verge.rs +++ b/src-tauri/src/data/verge.rs @@ -5,132 +5,132 @@ use serde::{Deserialize, Serialize}; /// ### `verge.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct Verge { - /// app listening port - /// for app singleton - pub app_singleton_port: Option, + /// app listening port + /// for app singleton + pub app_singleton_port: Option, - // i18n - pub language: Option, + // i18n + pub language: Option, - /// `light` or `dark` or `system` - pub theme_mode: Option, + /// `light` or `dark` or `system` + pub theme_mode: Option, - /// enable blur mode - /// maybe be able to set the alpha - pub theme_blur: Option, + /// enable blur mode + /// maybe be able to set the alpha + pub theme_blur: Option, - /// enable traffic graph default is true - pub traffic_graph: Option, + /// enable traffic graph default is true + pub traffic_graph: Option, - /// clash tun mode - pub enable_tun_mode: Option, + /// clash tun mode + pub enable_tun_mode: Option, - /// windows service mode - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_service_mode: Option, + /// windows service mode + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_service_mode: Option, - /// can the app auto startup - pub enable_auto_launch: Option, + /// can the app auto startup + pub enable_auto_launch: Option, - /// not show the window on launch - pub enable_silent_start: Option, + /// not show the window on launch + pub enable_silent_start: Option, - /// set system proxy - pub enable_system_proxy: Option, + /// set system proxy + pub enable_system_proxy: Option, - /// enable proxy guard - pub enable_proxy_guard: Option, + /// enable proxy guard + pub enable_proxy_guard: Option, - /// set system proxy bypass - pub system_proxy_bypass: Option, + /// set system proxy bypass + pub system_proxy_bypass: Option, - /// proxy guard duration - pub proxy_guard_duration: Option, + /// proxy guard duration + pub proxy_guard_duration: Option, - /// theme setting - pub theme_setting: Option, + /// theme setting + pub theme_setting: Option, - /// web ui list - pub web_ui_list: Option>, + /// web ui list + pub web_ui_list: Option>, - /// clash core path - #[serde(skip_serializing_if = "Option::is_none")] - pub clash_core: Option, + /// clash core path + #[serde(skip_serializing_if = "Option::is_none")] + pub clash_core: Option, - /// hotkey map - /// format: {func},{key} - pub hotkeys: Option>, + /// hotkey map + /// format: {func},{key} + pub hotkeys: Option>, - /// 切换代理时自动关闭连接 - pub auto_close_connection: Option, + /// 切换代理时自动关闭连接 + pub auto_close_connection: Option, - /// 默认的延迟测试连接 - pub default_latency_test: Option, + /// 默认的延迟测试连接 + pub default_latency_test: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct VergeTheme { - pub primary_color: Option, - pub secondary_color: Option, - pub primary_text: Option, - pub secondary_text: Option, + pub primary_color: Option, + pub secondary_color: Option, + pub primary_text: Option, + pub secondary_text: Option, - pub info_color: Option, - pub error_color: Option, - pub warning_color: Option, - pub success_color: Option, + pub info_color: Option, + pub error_color: Option, + pub warning_color: Option, + pub success_color: Option, - pub font_family: Option, - pub css_injection: Option, + pub font_family: Option, + pub css_injection: Option, } impl Verge { - pub fn new() -> Self { - config::read_yaml::(dirs::verge_path()) - } - - /// Save Verge App Config - pub fn save_file(&self) -> Result<()> { - config::save_yaml( - dirs::verge_path(), - self, - Some("# The Config for Clash Verge App\n\n"), - ) - } - - /// patch verge config - /// only save to file - pub fn patch_config(&mut self, patch: Verge) -> Result<()> { - macro_rules! patch { - ($key: tt) => { - if patch.$key.is_some() { - self.$key = patch.$key; - } - }; + pub fn new() -> Self { + config::read_yaml::(dirs::verge_path()) } - patch!(language); - patch!(theme_mode); - patch!(theme_blur); - patch!(traffic_graph); + /// Save Verge App Config + pub fn save_file(&self) -> Result<()> { + config::save_yaml( + dirs::verge_path(), + self, + Some("# The Config for Clash Verge App\n\n"), + ) + } - patch!(enable_tun_mode); - patch!(enable_service_mode); - patch!(enable_auto_launch); - patch!(enable_silent_start); - patch!(enable_system_proxy); - patch!(enable_proxy_guard); - patch!(system_proxy_bypass); - patch!(proxy_guard_duration); + /// patch verge config + /// only save to file + pub fn patch_config(&mut self, patch: Verge) -> Result<()> { + macro_rules! patch { + ($key: tt) => { + if patch.$key.is_some() { + self.$key = patch.$key; + } + }; + } - patch!(theme_setting); - patch!(web_ui_list); - patch!(clash_core); - patch!(hotkeys); + patch!(language); + patch!(theme_mode); + patch!(theme_blur); + patch!(traffic_graph); - patch!(auto_close_connection); - patch!(default_latency_test); + patch!(enable_tun_mode); + patch!(enable_service_mode); + patch!(enable_auto_launch); + patch!(enable_silent_start); + patch!(enable_system_proxy); + patch!(enable_proxy_guard); + patch!(system_proxy_bypass); + patch!(proxy_guard_duration); - self.save_file() - } + patch!(theme_setting); + patch!(web_ui_list); + patch!(clash_core); + patch!(hotkeys); + + patch!(auto_close_connection); + patch!(default_latency_test); + + self.save_file() + } } diff --git a/src-tauri/src/feat.rs b/src-tauri/src/feat.rs index 5bf1d49..79fade1 100644 --- a/src-tauri/src/feat.rs +++ b/src-tauri/src/feat.rs @@ -4,99 +4,99 @@ use crate::log_if_err; // 重启clash pub fn restart_clash_core() { - let core = Core::global(); - let mut service = core.service.lock(); - log_if_err!(service.restart()); - drop(service); - log_if_err!(core.activate()); + let core = Core::global(); + let mut service = core.service.lock(); + log_if_err!(service.restart()); + drop(service); + log_if_err!(core.activate()); } // 切换模式 pub fn change_clash_mode(mode: &str) { - let core = Core::global(); - log_if_err!(core.update_mode(mode)); + let core = Core::global(); + log_if_err!(core.update_mode(mode)); } // 切换系统代理 pub fn toggle_system_proxy() { - let core = Core::global(); - let data = Data::global(); + let core = Core::global(); + let data = Data::global(); - let verge = data.verge.lock(); - let enable = !verge.enable_system_proxy.clone().unwrap_or(false); - drop(verge); + let verge = data.verge.lock(); + let enable = !verge.enable_system_proxy.clone().unwrap_or(false); + drop(verge); - log_if_err!(core.patch_verge(Verge { - enable_system_proxy: Some(enable), - ..Verge::default() - })); + log_if_err!(core.patch_verge(Verge { + enable_system_proxy: Some(enable), + ..Verge::default() + })); - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + let handle = core.handle.lock(); + let _ = handle.refresh_verge(); } // 打开系统代理 pub fn enable_system_proxy() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { - enable_system_proxy: Some(true), - ..Verge::default() - })); + let core = Core::global(); + log_if_err!(core.patch_verge(Verge { + enable_system_proxy: Some(true), + ..Verge::default() + })); - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + let handle = core.handle.lock(); + let _ = handle.refresh_verge(); } // 关闭系统代理 pub fn disable_system_proxy() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { - enable_system_proxy: Some(false), - ..Verge::default() - })); + let core = Core::global(); + log_if_err!(core.patch_verge(Verge { + enable_system_proxy: Some(false), + ..Verge::default() + })); - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + let handle = core.handle.lock(); + let _ = handle.refresh_verge(); } // 切换tun模式 pub fn toggle_tun_mode() { - let core = Core::global(); - let data = Data::global(); + let core = Core::global(); + let data = Data::global(); - let verge = data.verge.lock(); - let enable = !verge.enable_tun_mode.clone().unwrap_or(false); - drop(verge); + let verge = data.verge.lock(); + let enable = !verge.enable_tun_mode.clone().unwrap_or(false); + drop(verge); - log_if_err!(core.patch_verge(Verge { - enable_tun_mode: Some(enable), - ..Verge::default() - })); + log_if_err!(core.patch_verge(Verge { + enable_tun_mode: Some(enable), + ..Verge::default() + })); - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + let handle = core.handle.lock(); + let _ = handle.refresh_verge(); } // 打开tun模式 pub fn enable_tun_mode() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { - enable_tun_mode: Some(true), - ..Verge::default() - })); + let core = Core::global(); + log_if_err!(core.patch_verge(Verge { + enable_tun_mode: Some(true), + ..Verge::default() + })); - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + let handle = core.handle.lock(); + let _ = handle.refresh_verge(); } // 关闭tun模式 pub fn disable_tun_mode() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { - enable_tun_mode: Some(false), - ..Verge::default() - })); + let core = Core::global(); + log_if_err!(core.patch_verge(Verge { + enable_tun_mode: Some(false), + ..Verge::default() + })); - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + let handle = core.handle.lock(); + let _ = handle.refresh_verge(); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0c9dd67..066939b 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; @@ -14,129 +14,129 @@ use crate::utils::{init, resolve, server}; use tauri::{api, Manager, SystemTray}; fn main() -> std::io::Result<()> { - // 单例检测 - if server::check_singleton().is_err() { - println!("app exists"); - return Ok(()); - } - - #[cfg(target_os = "windows")] - unsafe { - use crate::utils::dirs; - dirs::init_portable_flag(); - } - - crate::log_if_err!(init::init_config()); - - #[allow(unused_mut)] - let mut builder = tauri::Builder::default() - .setup(|app| Ok(resolve::resolve_setup(app))) - .system_tray(SystemTray::new()) - .on_system_tray_event(core::tray::Tray::on_system_tray_event) - .invoke_handler(tauri::generate_handler![ - // common - cmds::get_sys_proxy, - cmds::open_app_dir, - cmds::open_logs_dir, - cmds::open_web_url, - cmds::kill_sidecar, - cmds::restart_sidecar, - // clash - cmds::get_clash_info, - cmds::get_clash_logs, - cmds::patch_clash_config, - cmds::change_clash_core, - cmds::get_runtime_config, - cmds::get_runtime_yaml, - cmds::get_runtime_exists, - cmds::get_runtime_logs, - // verge - cmds::get_verge_config, - cmds::patch_verge_config, - cmds::update_hotkeys, - // 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}; - - builder = builder.menu( - Menu::new().add_submenu(Submenu::new( - "Edit", - 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) - .add_native_item(MenuItem::CloseWindow) - .add_native_item(MenuItem::Quit) - )), - ); - } - - #[allow(unused_mut)] - let mut app = builder - .build(tauri::generate_context!()) - .expect("error while running tauri application"); - - #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Accessory); - - let app_handle = app.app_handle(); - ctrlc::set_handler(move || { - resolve::resolve_reset(); - app_handle.exit(0); - }) - .expect("error while exiting."); - - #[allow(unused)] - app.run(|app_handle, e| match e { - tauri::RunEvent::ExitRequested { api, .. } => { - api.prevent_exit(); + // 单例检测 + if server::check_singleton().is_err() { + println!("app exists"); + return Ok(()); } - tauri::RunEvent::Exit => { - resolve::resolve_reset(); - api::process::kill_children(); - app_handle.exit(0); + + #[cfg(target_os = "windows")] + unsafe { + use crate::utils::dirs; + dirs::init_portable_flag(); } + + crate::log_if_err!(init::init_config()); + + #[allow(unused_mut)] + let mut builder = tauri::Builder::default() + .setup(|app| Ok(resolve::resolve_setup(app))) + .system_tray(SystemTray::new()) + .on_system_tray_event(core::tray::Tray::on_system_tray_event) + .invoke_handler(tauri::generate_handler![ + // common + cmds::get_sys_proxy, + cmds::open_app_dir, + cmds::open_logs_dir, + cmds::open_web_url, + cmds::kill_sidecar, + cmds::restart_sidecar, + // clash + cmds::get_clash_info, + cmds::get_clash_logs, + cmds::patch_clash_config, + cmds::change_clash_core, + cmds::get_runtime_config, + cmds::get_runtime_yaml, + cmds::get_runtime_exists, + cmds::get_runtime_logs, + // verge + cmds::get_verge_config, + cmds::patch_verge_config, + cmds::update_hotkeys, + // 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")] - tauri::RunEvent::WindowEvent { label, event, .. } => { - if label == "main" { - match event { - tauri::WindowEvent::CloseRequested { api, .. } => { - api.prevent_close(); - app_handle.get_window("main").map(|win| { - let _ = win.hide(); - }); - } - _ => {} - } - } - } - _ => {} - }); + { + use tauri::{Menu, MenuItem, Submenu}; - Ok(()) + builder = builder.menu( + Menu::new().add_submenu(Submenu::new( + "Edit", + 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) + .add_native_item(MenuItem::CloseWindow) + .add_native_item(MenuItem::Quit), + )), + ); + } + + #[allow(unused_mut)] + let mut app = builder + .build(tauri::generate_context!()) + .expect("error while running tauri application"); + + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Accessory); + + let app_handle = app.app_handle(); + ctrlc::set_handler(move || { + resolve::resolve_reset(); + app_handle.exit(0); + }) + .expect("error while exiting."); + + #[allow(unused)] + app.run(|app_handle, e| match e { + tauri::RunEvent::ExitRequested { api, .. } => { + api.prevent_exit(); + } + tauri::RunEvent::Exit => { + resolve::resolve_reset(); + api::process::kill_children(); + app_handle.exit(0); + } + #[cfg(target_os = "macos")] + tauri::RunEvent::WindowEvent { label, event, .. } => { + if label == "main" { + match event { + tauri::WindowEvent::CloseRequested { api, .. } => { + api.prevent_close(); + app_handle.get_window("main").map(|win| { + let _ = win.hide(); + }); + } + _ => {} + } + } + } + _ => {} + }); + + Ok(()) } diff --git a/src-tauri/src/utils/config.rs b/src-tauri/src/utils/config.rs index add4d8b..17fe14b 100644 --- a/src-tauri/src/utils/config.rs +++ b/src-tauri/src/utils/config.rs @@ -5,55 +5,55 @@ use std::{fs, path::PathBuf}; /// read data from yaml as struct T pub fn read_yaml(path: PathBuf) -> T { - if !path.exists() { - log::error!(target: "app", "file not found \"{}\"", path.display()); - return T::default(); - } - - let yaml_str = fs::read_to_string(&path).unwrap_or("".into()); - - match serde_yaml::from_str::(&yaml_str) { - Ok(val) => val, - Err(_) => { - log::error!(target: "app", "failed to read yaml file \"{}\"", path.display()); - T::default() + if !path.exists() { + log::error!(target: "app", "file not found \"{}\"", path.display()); + return T::default(); + } + + let yaml_str = fs::read_to_string(&path).unwrap_or("".into()); + + match serde_yaml::from_str::(&yaml_str) { + Ok(val) => val, + Err(_) => { + log::error!(target: "app", "failed to read yaml file \"{}\"", path.display()); + T::default() + } } - } } /// read mapping from yaml fix #165 pub fn read_merge_mapping(path: PathBuf) -> Mapping { - let map = Mapping::new(); + let map = Mapping::new(); - if !path.exists() { - log::error!(target: "app", "file not found \"{}\"", path.display()); - return map; - } - - let yaml_str = fs::read_to_string(&path).unwrap_or("".into()); - - match serde_yaml::from_str::(&yaml_str) { - Ok(mut val) => { - crate::log_if_err!(val.apply_merge()); - val.as_mapping().unwrap_or(&map).to_owned() + if !path.exists() { + log::error!(target: "app", "file not found \"{}\"", path.display()); + return map; } - Err(_) => { - log::error!(target: "app", "failed to read yaml file \"{}\"", path.display()); - map + + let yaml_str = fs::read_to_string(&path).unwrap_or("".into()); + + match serde_yaml::from_str::(&yaml_str) { + Ok(mut val) => { + crate::log_if_err!(val.apply_merge()); + val.as_mapping().unwrap_or(&map).to_owned() + } + Err(_) => { + log::error!(target: "app", "failed to read yaml file \"{}\"", path.display()); + map + } } - } } /// save the data to the file /// can set `prefix` string to add some comments pub fn save_yaml(path: PathBuf, data: &T, prefix: Option<&str>) -> Result<()> { - let data_str = serde_yaml::to_string(data)?; + let data_str = serde_yaml::to_string(data)?; - let yaml_str = match prefix { - Some(prefix) => format!("{prefix}{data_str}"), - None => data_str, - }; + let yaml_str = match prefix { + Some(prefix) => format!("{prefix}{data_str}"), + None => data_str, + }; - let path_str = path.as_os_str().to_string_lossy().to_string(); - fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\"")) + let path_str = path.as_os_str().to_string_lossy().to_string(); + fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\"")) } diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index b0cdca1..b810180 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -1,8 +1,8 @@ use std::env::temp_dir; use std::path::PathBuf; use tauri::{ - api::path::{home_dir, resource_dir}, - Env, PackageInfo, + api::path::{home_dir, resource_dir}, + Env, PackageInfo, }; #[cfg(not(feature = "verge-dev"))] @@ -26,89 +26,89 @@ pub static mut APP_VERSION: &str = "v1.1.1"; /// initialize portable flag #[allow(unused)] pub unsafe fn init_portable_flag() { - #[cfg(target_os = "windows")] - { - use tauri::utils::platform::current_exe; + #[cfg(target_os = "windows")] + { + use tauri::utils::platform::current_exe; - let exe = current_exe().unwrap(); - let dir = exe.parent().unwrap(); - let dir = PathBuf::from(dir).join(".config/PORTABLE"); + let exe = current_exe().unwrap(); + let dir = exe.parent().unwrap(); + let dir = PathBuf::from(dir).join(".config/PORTABLE"); - if dir.exists() { - PORTABLE_FLAG = true; + if dir.exists() { + PORTABLE_FLAG = true; + } } - } } /// get the verge app home dir pub fn app_home_dir() -> PathBuf { - #[cfg(target_os = "windows")] - unsafe { - use tauri::utils::platform::current_exe; + #[cfg(target_os = "windows")] + unsafe { + use tauri::utils::platform::current_exe; - if !PORTABLE_FLAG { - home_dir().unwrap().join(".config").join(APP_DIR) - } else { - let app_exe = current_exe().unwrap(); - let app_exe = dunce::canonicalize(app_exe).unwrap(); - let app_dir = app_exe.parent().unwrap(); - PathBuf::from(app_dir).join(".config").join(APP_DIR) + if !PORTABLE_FLAG { + home_dir().unwrap().join(".config").join(APP_DIR) + } else { + let app_exe = current_exe().unwrap(); + let app_exe = dunce::canonicalize(app_exe).unwrap(); + let app_dir = app_exe.parent().unwrap(); + PathBuf::from(app_dir).join(".config").join(APP_DIR) + } } - } - #[cfg(not(target_os = "windows"))] - home_dir().unwrap().join(".config").join(APP_DIR) + #[cfg(not(target_os = "windows"))] + home_dir().unwrap().join(".config").join(APP_DIR) } /// get the resources dir pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf { - let res_dir = resource_dir(package_info, &Env::default()) - .unwrap() - .join("resources"); + let res_dir = resource_dir(package_info, &Env::default()) + .unwrap() + .join("resources"); - unsafe { - RESOURCE_DIR = Some(res_dir.clone()); + unsafe { + RESOURCE_DIR = Some(res_dir.clone()); - let ver = package_info.version.to_string(); - let ver_str = format!("v{ver}"); - APP_VERSION = Box::leak(Box::new(ver_str)); - } + let ver = package_info.version.to_string(); + let ver_str = format!("v{ver}"); + APP_VERSION = Box::leak(Box::new(ver_str)); + } - res_dir + res_dir } /// profiles dir pub fn app_profiles_dir() -> PathBuf { - app_home_dir().join("profiles") + app_home_dir().join("profiles") } /// logs dir pub fn app_logs_dir() -> PathBuf { - app_home_dir().join("logs") + app_home_dir().join("logs") } pub fn clash_path() -> PathBuf { - app_home_dir().join(CLASH_CONFIG) + app_home_dir().join(CLASH_CONFIG) } pub fn verge_path() -> PathBuf { - app_home_dir().join(VERGE_CONFIG) + app_home_dir().join(VERGE_CONFIG) } pub fn profiles_path() -> PathBuf { - app_home_dir().join(PROFILE_YAML) + app_home_dir().join(PROFILE_YAML) } pub fn profiles_temp_path() -> PathBuf { - #[cfg(not(feature = "debug-yml"))] - return temp_dir().join(PROFILE_TEMP); + #[cfg(not(feature = "debug-yml"))] + return temp_dir().join(PROFILE_TEMP); - #[cfg(feature = "debug-yml")] - return app_home_dir().join(PROFILE_TEMP); + #[cfg(feature = "debug-yml")] + return app_home_dir().join(PROFILE_TEMP); } pub fn clash_pid_path() -> PathBuf { - unsafe { RESOURCE_DIR.clone().unwrap().join("clash.pid") } + unsafe { RESOURCE_DIR.clone().unwrap().join("clash.pid") } } #[cfg(windows)] @@ -116,23 +116,23 @@ static SERVICE_PATH: &str = "clash-verge-service.exe"; #[cfg(windows)] pub fn service_path() -> PathBuf { - unsafe { - let res_dir = RESOURCE_DIR.clone().unwrap(); - res_dir.join(SERVICE_PATH) - } + unsafe { + let res_dir = RESOURCE_DIR.clone().unwrap(); + res_dir.join(SERVICE_PATH) + } } #[cfg(windows)] pub fn service_log_file() -> PathBuf { - use chrono::Local; + use chrono::Local; - let log_dir = app_logs_dir().join("service"); + let log_dir = app_logs_dir().join("service"); - let local_time = Local::now().format("%Y-%m-%d-%H%M%S").to_string(); - let log_file = format!("{}.log", local_time); - let log_file = log_dir.join(log_file); + let local_time = Local::now().format("%Y-%m-%d-%H%M%S").to_string(); + let log_file = format!("{}.log", local_time); + let log_file = log_dir.join(log_file); - std::fs::create_dir_all(&log_dir).unwrap(); + std::fs::create_dir_all(&log_dir).unwrap(); - log_file + log_file } diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index b38ed90..b8ae558 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -6,67 +6,67 @@ use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; pub fn get_now() -> usize { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() as _ + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as _ } const ALPHABET: [char; 62] = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', - 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', - 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', - 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z', ]; /// generate the uid pub fn get_uid(prefix: &str) -> String { - let id = nanoid!(11, &ALPHABET); - format!("{prefix}{id}") + let id = nanoid!(11, &ALPHABET); + format!("{prefix}{id}") } /// parse the string /// xxx=123123; => 123123 pub fn parse_str(target: &str, key: &str) -> Option { - target.find(key).and_then(|idx| { - let idx = idx + key.len(); - let value = &target[idx..]; + target.find(key).and_then(|idx| { + let idx = idx + key.len(); + let value = &target[idx..]; - match value.split(';').nth(0) { - Some(value) => value.trim().parse(), - None => value.trim().parse(), - } - .ok() - }) + match value.split(';').nth(0) { + Some(value) => value.trim().parse(), + None => value.trim().parse(), + } + .ok() + }) } /// open file /// use vscode by default pub fn open_file(path: PathBuf) -> Result<()> { - // use vscode first - if let Ok(code) = which::which("code") { - let mut command = Command::new(&code); + // use vscode first + if let Ok(code) = which::which("code") { + let mut command = Command::new(&code); - #[cfg(target_os = "windows")] - { - use std::os::windows::process::CommandExt; - if let Err(err) = command.creation_flags(0x08000000).arg(&path).spawn() { - log::error!(target: "app", "failed to open with VScode `{err}`"); - open::that(path)?; - } + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + if let Err(err) = command.creation_flags(0x08000000).arg(&path).spawn() { + log::error!(target: "app", "failed to open with VScode `{err}`"); + open::that(path)?; + } + } + + #[cfg(not(target_os = "windows"))] + if let Err(err) = command.arg(&path).spawn() { + log::error!(target: "app", "failed to open with VScode `{err}`"); + open::that(path)?; + } + + return Ok(()); } - #[cfg(not(target_os = "windows"))] - if let Err(err) = command.arg(&path).spawn() { - log::error!(target: "app", "failed to open with VScode `{err}`"); - open::that(path)?; - } - - return Ok(()); - } - - open::that(path)?; - Ok(()) + open::that(path)?; + Ok(()) } #[macro_export] @@ -96,27 +96,27 @@ macro_rules! wrap_err { /// return the string literal error #[macro_export] macro_rules! ret_err { - ($str: expr) => { - return Err($str.into()) - }; + ($str: expr) => { + return Err($str.into()) + }; } #[test] fn test_parse_value() { - let test_1 = "upload=111; download=2222; total=3333; expire=444"; - let test_2 = "attachment; filename=Clash.yaml"; + let test_1 = "upload=111; download=2222; total=3333; expire=444"; + let test_2 = "attachment; filename=Clash.yaml"; - assert_eq!(parse_str::(test_1, "upload=").unwrap(), 111); - assert_eq!(parse_str::(test_1, "download=").unwrap(), 2222); - assert_eq!(parse_str::(test_1, "total=").unwrap(), 3333); - assert_eq!(parse_str::(test_1, "expire=").unwrap(), 444); - assert_eq!( - parse_str::(test_2, "filename=").unwrap(), - format!("Clash.yaml") - ); + assert_eq!(parse_str::(test_1, "upload=").unwrap(), 111); + assert_eq!(parse_str::(test_1, "download=").unwrap(), 2222); + assert_eq!(parse_str::(test_1, "total=").unwrap(), 3333); + assert_eq!(parse_str::(test_1, "expire=").unwrap(), 444); + assert_eq!( + parse_str::(test_2, "filename=").unwrap(), + format!("Clash.yaml") + ); - assert_eq!(parse_str::(test_1, "aaa="), None); - assert_eq!(parse_str::(test_1, "upload1="), None); - assert_eq!(parse_str::(test_1, "expire1="), None); - assert_eq!(parse_str::(test_2, "attachment="), None); + assert_eq!(parse_str::(test_1, "aaa="), None); + assert_eq!(parse_str::(test_1, "upload1="), None); + assert_eq!(parse_str::(test_1, "expire1="), None); + assert_eq!(parse_str::(test_2, "attachment="), None); } diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 6752cac..cadb64a 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -12,94 +12,94 @@ use tauri::PackageInfo; /// initialize this instance's log file fn init_log() -> Result<()> { - let log_dir = dirs::app_logs_dir(); - if !log_dir.exists() { - let _ = fs::create_dir_all(&log_dir); - } + let log_dir = dirs::app_logs_dir(); + if !log_dir.exists() { + let _ = fs::create_dir_all(&log_dir); + } - let local_time = Local::now().format("%Y-%m-%d-%H%M%S").to_string(); - let log_file = format!("{}.log", local_time); - let log_file = log_dir.join(log_file); + let local_time = Local::now().format("%Y-%m-%d-%H%M%S").to_string(); + let log_file = format!("{}.log", local_time); + let log_file = log_dir.join(log_file); - let time_format = "{d(%Y-%m-%d %H:%M:%S)} - {m}{n}"; - let stdout = ConsoleAppender::builder() - .encoder(Box::new(PatternEncoder::new(time_format))) - .build(); - let tofile = FileAppender::builder() - .encoder(Box::new(PatternEncoder::new(time_format))) - .build(log_file)?; + let time_format = "{d(%Y-%m-%d %H:%M:%S)} - {m}{n}"; + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new(time_format))) + .build(); + let tofile = FileAppender::builder() + .encoder(Box::new(PatternEncoder::new(time_format))) + .build(log_file)?; - let config = Config::builder() - .appender(Appender::builder().build("stdout", Box::new(stdout))) - .appender(Appender::builder().build("file", Box::new(tofile))) - .logger( - Logger::builder() - .appenders(["file", "stdout"]) - .additive(false) - .build("app", LevelFilter::Info), - ) - .build(Root::builder().appender("stdout").build(LevelFilter::Info))?; + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .appender(Appender::builder().build("file", Box::new(tofile))) + .logger( + Logger::builder() + .appenders(["file", "stdout"]) + .additive(false) + .build("app", LevelFilter::Info), + ) + .build(Root::builder().appender("stdout").build(LevelFilter::Info))?; - log4rs::init_config(config)?; + log4rs::init_config(config)?; - Ok(()) + Ok(()) } /// Initialize all the files from resources pub fn init_config() -> Result<()> { - let _ = init_log(); + let _ = init_log(); - let app_dir = dirs::app_home_dir(); - let profiles_dir = dirs::app_profiles_dir(); + let app_dir = dirs::app_home_dir(); + let profiles_dir = dirs::app_profiles_dir(); - if !app_dir.exists() { - let _ = fs::create_dir_all(&app_dir); - } - if !profiles_dir.exists() { - let _ = fs::create_dir_all(&profiles_dir); - } + if !app_dir.exists() { + let _ = fs::create_dir_all(&app_dir); + } + if !profiles_dir.exists() { + let _ = fs::create_dir_all(&profiles_dir); + } - // target path - let clash_path = app_dir.join("config.yaml"); - let verge_path = app_dir.join("verge.yaml"); - let profile_path = app_dir.join("profiles.yaml"); + // target path + let clash_path = app_dir.join("config.yaml"); + let verge_path = app_dir.join("verge.yaml"); + let profile_path = app_dir.join("profiles.yaml"); - if !clash_path.exists() { - fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?; - } - if !verge_path.exists() { - fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?; - } - if !profile_path.exists() { - fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?; - } - Ok(()) + if !clash_path.exists() { + fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?; + } + if !verge_path.exists() { + fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?; + } + if !profile_path.exists() { + fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?; + } + Ok(()) } /// initialize app pub fn init_resources(package_info: &PackageInfo) { - // create app dir - let app_dir = dirs::app_home_dir(); - let res_dir = dirs::app_resources_dir(package_info); + // create app dir + let app_dir = dirs::app_home_dir(); + let res_dir = dirs::app_resources_dir(package_info); - if !app_dir.exists() { - let _ = fs::create_dir_all(&app_dir); - } - - // copy the resource file - let mmdb_path = app_dir.join("Country.mmdb"); - let mmdb_tmpl = res_dir.join("Country.mmdb"); - if !mmdb_path.exists() && mmdb_tmpl.exists() { - let _ = fs::copy(mmdb_tmpl, mmdb_path); - } - - // copy the wintun.dll - #[cfg(target_os = "windows")] - { - let wintun_path = app_dir.join("wintun.dll"); - let wintun_tmpl = res_dir.join("wintun.dll"); - if !wintun_path.exists() && wintun_tmpl.exists() { - let _ = fs::copy(wintun_tmpl, wintun_path); + if !app_dir.exists() { + let _ = fs::create_dir_all(&app_dir); + } + + // copy the resource file + let mmdb_path = app_dir.join("Country.mmdb"); + let mmdb_tmpl = res_dir.join("Country.mmdb"); + if !mmdb_path.exists() && mmdb_tmpl.exists() { + let _ = fs::copy(mmdb_tmpl, mmdb_path); + } + + // copy the wintun.dll + #[cfg(target_os = "windows")] + { + let wintun_path = app_dir.join("wintun.dll"); + let wintun_tmpl = res_dir.join("wintun.dll"); + if !wintun_path.exists() && wintun_tmpl.exists() { + let _ = fs::copy(wintun_tmpl, wintun_path); + } } - } } diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index aa1ba32..16ff18f 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,110 +1,110 @@ use crate::{ - core::{tray, Core}, - data::Data, - utils::init, - utils::server, + core::{tray, Core}, + data::Data, + utils::init, + utils::server, }; use tauri::{App, AppHandle, Manager}; /// handle something when start app pub fn resolve_setup(app: &App) { - let _ = app - .tray_handle() - .set_menu(tray::Tray::tray_menu(&app.app_handle())); + let _ = app + .tray_handle() + .set_menu(tray::Tray::tray_menu(&app.app_handle())); - init::init_resources(app.package_info()); + init::init_resources(app.package_info()); - let silent_start = { - let global = Data::global(); - let verge = global.verge.lock(); - let singleton = verge.app_singleton_port.clone(); + let silent_start = { + let global = Data::global(); + let verge = global.verge.lock(); + let singleton = verge.app_singleton_port.clone(); - // setup a simple http server for singleton - server::embed_server(&app.app_handle(), singleton); + // setup a simple http server for singleton + server::embed_server(&app.app_handle(), singleton); - verge.enable_silent_start.clone().unwrap_or(false) - }; + verge.enable_silent_start.clone().unwrap_or(false) + }; - // core should be initialized after init_app fix #122 - let core = Core::global(); - core.init(app.app_handle()); + // core should be initialized after init_app fix #122 + let core = Core::global(); + core.init(app.app_handle()); - if !silent_start { - create_window(&app.app_handle()); - } + if !silent_start { + create_window(&app.app_handle()); + } } /// reset system proxy pub fn resolve_reset() { - let core = Core::global(); - let mut sysopt = core.sysopt.lock(); - crate::log_if_err!(sysopt.reset_sysproxy()); - drop(sysopt); + let core = Core::global(); + let mut sysopt = core.sysopt.lock(); + crate::log_if_err!(sysopt.reset_sysproxy()); + drop(sysopt); - let mut service = core.service.lock(); - crate::log_if_err!(service.stop()); + let mut service = core.service.lock(); + crate::log_if_err!(service.stop()); } /// create main window pub fn create_window(app_handle: &AppHandle) { - if let Some(window) = app_handle.get_window("main") { - let _ = window.unminimize(); - let _ = window.show(); - let _ = window.set_focus(); - return; - } - - let builder = tauri::window::WindowBuilder::new( - app_handle, - "main".to_string(), - tauri::WindowUrl::App("index.html".into()), - ) - .title("Clash Verge") - .center() - .fullscreen(false) - .min_inner_size(600.0, 520.0); - - #[cfg(target_os = "windows")] - { - use crate::utils::winhelp; - use std::time::Duration; - use tokio::time::sleep; - use window_shadows::set_shadow; - use window_vibrancy::apply_blur; - - match builder - .decorations(false) - .transparent(true) - .inner_size(800.0, 636.0) - .build() - { - Ok(_) => { - let app_handle = app_handle.clone(); - - tauri::async_runtime::spawn(async move { - sleep(Duration::from_secs(1)).await; - - if let Some(window) = app_handle.get_window("main") { - let _ = window.show(); - let _ = set_shadow(&window, true); - - if !winhelp::is_win11() { - let _ = apply_blur(&window, None); - } - } - }); - } - Err(err) => log::error!(target: "app", "{err}"), + if let Some(window) = app_handle.get_window("main") { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + return; } - } - #[cfg(target_os = "macos")] - crate::log_if_err!(builder.decorations(true).inner_size(800.0, 642.0).build()); + let builder = tauri::window::WindowBuilder::new( + app_handle, + "main".to_string(), + tauri::WindowUrl::App("index.html".into()), + ) + .title("Clash Verge") + .center() + .fullscreen(false) + .min_inner_size(600.0, 520.0); - #[cfg(target_os = "linux")] - crate::log_if_err!(builder - .decorations(false) - .transparent(true) - .inner_size(800.0, 636.0) - .build()); + #[cfg(target_os = "windows")] + { + use crate::utils::winhelp; + use std::time::Duration; + use tokio::time::sleep; + use window_shadows::set_shadow; + use window_vibrancy::apply_blur; + + match builder + .decorations(false) + .transparent(true) + .inner_size(800.0, 636.0) + .build() + { + Ok(_) => { + let app_handle = app_handle.clone(); + + tauri::async_runtime::spawn(async move { + sleep(Duration::from_secs(1)).await; + + if let Some(window) = app_handle.get_window("main") { + let _ = window.show(); + let _ = set_shadow(&window, true); + + if !winhelp::is_win11() { + let _ = apply_blur(&window, None); + } + } + }); + } + Err(err) => log::error!(target: "app", "{err}"), + } + } + + #[cfg(target_os = "macos")] + crate::log_if_err!(builder.decorations(true).inner_size(800.0, 642.0).build()); + + #[cfg(target_os = "linux")] + crate::log_if_err!(builder + .decorations(false) + .transparent(true) + .inner_size(800.0, 636.0) + .build()); } diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index 26caaf0..c0162c6 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -13,32 +13,32 @@ const SERVER_PORT: u16 = 11233; /// check whether there is already exists pub fn check_singleton() -> Result<(), ()> { - let verge = Verge::new(); - let port = verge.app_singleton_port.unwrap_or(SERVER_PORT); + let verge = Verge::new(); + let port = verge.app_singleton_port.unwrap_or(SERVER_PORT); - if !local_port_available(port) { - tauri::async_runtime::block_on(async { - let url = format!("http://127.0.0.1:{}/commands/visible", port); - reqwest::get(url).await.unwrap(); - Err(()) - }) - } else { - Ok(()) - } + if !local_port_available(port) { + tauri::async_runtime::block_on(async { + let url = format!("http://127.0.0.1:{}/commands/visible", port); + reqwest::get(url).await.unwrap(); + Err(()) + }) + } else { + Ok(()) + } } /// The embed server only be used to implement singleton process /// maybe it can be used as pac server later pub fn embed_server(app_handle: &AppHandle, port: Option) { - let app_handle = app_handle.clone(); - let port = port.unwrap_or(SERVER_PORT); + let app_handle = app_handle.clone(); + let port = port.unwrap_or(SERVER_PORT); - tauri::async_runtime::spawn(async move { - let commands = warp::path!("commands" / "visible").map(move || { - resolve::create_window(&app_handle); - return format!("ok"); + tauri::async_runtime::spawn(async move { + let commands = warp::path!("commands" / "visible").map(move || { + resolve::create_window(&app_handle); + return format!("ok"); + }); + + warp::serve(commands).bind(([127, 0, 0, 1], port)).await; }); - - warp::serve(commands).bind(([127, 0, 0, 1], port)).await; - }); } diff --git a/src-tauri/src/utils/winhelp.rs b/src-tauri/src/utils/winhelp.rs index 8abd7b5..e903d95 100644 --- a/src-tauri/src/utils/winhelp.rs +++ b/src-tauri/src/utils/winhelp.rs @@ -1,69 +1,69 @@ -#![cfg(target_os = "windows")] -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] - -//! -//! From https://github.com/tauri-apps/window-vibrancy/blob/dev/src/windows.rs -//! - -use windows_sys::Win32::{ - Foundation::*, - System::{LibraryLoader::*, SystemInformation::*}, -}; - -fn get_function_impl(library: &str, function: &str) -> Option { - assert_eq!(library.chars().last(), Some('\0')); - assert_eq!(function.chars().last(), Some('\0')); - - let module = unsafe { LoadLibraryA(library.as_ptr()) }; - if module == 0 { - return None; - } - Some(unsafe { GetProcAddress(module, function.as_ptr()) }) -} - -macro_rules! get_function { - ($lib:expr, $func:ident) => { - get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe { - std::mem::transmute::<::windows_sys::Win32::Foundation::FARPROC, $func>(f) - }) - }; -} - -/// Returns a tuple of (major, minor, buildnumber) -fn get_windows_ver() -> Option<(u32, u32, u32)> { - type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32; - let handle = get_function!("ntdll.dll", RtlGetVersion); - if let Some(rtl_get_version) = handle { - unsafe { - let mut vi = OSVERSIONINFOW { - dwOSVersionInfoSize: 0, - dwMajorVersion: 0, - dwMinorVersion: 0, - dwBuildNumber: 0, - dwPlatformId: 0, - szCSDVersion: [0; 128], - }; - - let status = (rtl_get_version)(&mut vi as _); - - if status >= 0 { - Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber)) - } else { - None - } - } - } else { - None - } -} - -pub fn is_win11() -> bool { - let v = get_windows_ver().unwrap_or_default(); - v.2 >= 22000 -} - -#[test] -fn test_version() { - dbg!(get_windows_ver().unwrap_or_default()); -} +#![cfg(target_os = "windows")] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +//! +//! From https://github.com/tauri-apps/window-vibrancy/blob/dev/src/windows.rs +//! + +use windows_sys::Win32::{ + Foundation::*, + System::{LibraryLoader::*, SystemInformation::*}, +}; + +fn get_function_impl(library: &str, function: &str) -> Option { + assert_eq!(library.chars().last(), Some('\0')); + assert_eq!(function.chars().last(), Some('\0')); + + let module = unsafe { LoadLibraryA(library.as_ptr()) }; + if module == 0 { + return None; + } + Some(unsafe { GetProcAddress(module, function.as_ptr()) }) +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe { + std::mem::transmute::<::windows_sys::Win32::Foundation::FARPROC, $func>(f) + }) + }; +} + +/// Returns a tuple of (major, minor, buildnumber) +fn get_windows_ver() -> Option<(u32, u32, u32)> { + type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32; + let handle = get_function!("ntdll.dll", RtlGetVersion); + if let Some(rtl_get_version) = handle { + unsafe { + let mut vi = OSVERSIONINFOW { + dwOSVersionInfoSize: 0, + dwMajorVersion: 0, + dwMinorVersion: 0, + dwBuildNumber: 0, + dwPlatformId: 0, + szCSDVersion: [0; 128], + }; + + let status = (rtl_get_version)(&mut vi as _); + + if status >= 0 { + Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber)) + } else { + None + } + } + } else { + None + } +} + +pub fn is_win11() -> bool { + let v = get_windows_ver().unwrap_or_default(); + v.2 >= 22000 +} + +#[test] +fn test_version() { + dbg!(get_windows_ver().unwrap_or_default()); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index cfd09d4..55cb11f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -25,13 +25,8 @@ "icons/icon-new.icns", "icons/icon.ico" ], - "resources": [ - "resources" - ], - "externalBin": [ - "sidecar/clash", - "sidecar/clash-meta" - ], + "resources": ["resources"], + "externalBin": ["sidecar/clash", "sidecar/clash-meta"], "copyright": "© 2022 zzzgydi All Rights Reserved", "category": "DeveloperTool", "shortDescription": "A Clash GUI based on tauri.", @@ -51,10 +46,7 @@ "digestAlgorithm": "sha256", "timestampUrl": "", "wix": { - "language": [ - "zh-CN", - "en-US" - ] + "language": ["zh-CN", "en-US"] } } }, @@ -86,4 +78,4 @@ "csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src data: 'self';" } } -} \ No newline at end of file +}