diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index f3aa877..a39bfc4 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -178,8 +178,12 @@ pub fn get_clash_info(core: State<'_, Core>) -> CmdResult { /// after putting the change to the clash core /// then we should save the latest config #[tauri::command] -pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult { - wrap_err!(core.patch_clash(payload)) +pub fn patch_clash_config( + payload: Mapping, + app_handle: tauri::AppHandle, + core: State<'_, Core>, +) -> CmdResult { + wrap_err!(core.patch_clash(payload, &app_handle)) } /// get the verge config diff --git a/src-tauri/src/core/clash.rs b/src-tauri/src/core/clash.rs index 4d2cc0a..d73c574 100644 --- a/src-tauri/src/core/clash.rs +++ b/src-tauri/src/core/clash.rs @@ -16,6 +16,9 @@ pub struct ClashInfo { /// clash secret pub secret: Option, + + /// mode + pub mode: Option, } impl ClashInfo { @@ -26,6 +29,7 @@ impl ClashInfo { let key_port_2 = Value::from("mixed-port"); let key_server = Value::from("external-controller"); let key_secret = Value::from("secret"); + let key_mode = Value::from("mode"); let port = match config.get(&key_port_1) { Some(value) => match value { @@ -72,11 +76,20 @@ impl ClashInfo { _ => None, }; + let mode = match config.get(&key_mode) { + Some(value) => match value { + Value::String(val_str) => Some(val_str.clone()), + _ => None, + }, + _ => None, + }; + ClashInfo { status: "init".into(), port, server, secret, + mode, } } } @@ -113,20 +126,26 @@ impl Clash { /// patch update the clash config /// if the port is changed then return true - pub fn patch_config(&mut self, patch: Mapping) -> Result { + pub fn patch_config(&mut self, patch: Mapping) -> Result<(bool, bool)> { let port_key = Value::from("mixed-port"); let server_key = Value::from("external-controller"); let secret_key = Value::from("secret"); + let mode_key = Value::from("mode"); let mut change_port = false; let mut change_info = false; + let mut change_mode = false; for (key, value) in patch.into_iter() { if key == port_key { change_port = true; } - if key == port_key || key == server_key || key == secret_key { + if key == mode_key { + change_mode = true; + } + + if key == port_key || key == server_key || key == secret_key || key == mode_key { change_info = true; } @@ -139,7 +158,7 @@ impl Clash { self.save_config()?; - Ok(change_port) + Ok((change_port, change_mode)) } /// revise the `tun` and `dns` config diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index f9abfe9..b6cb1d8 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -7,6 +7,7 @@ use crate::utils::{dirs, help}; use anyhow::{bail, Result}; use parking_lot::Mutex; use serde_yaml::Mapping; +use serde_yaml::Value; use std::sync::Arc; use std::time::Duration; use tauri::{AppHandle, Manager, Window}; @@ -168,15 +169,15 @@ impl Core { /// Patch Clash /// handle the clash config changed - pub fn patch_clash(&self, patch: Mapping) -> Result<()> { - let (changed, port) = { + pub fn patch_clash(&self, patch: Mapping, app_handle: &AppHandle) -> Result<()> { + let ((changed_port, changed_mode), port) = { let mut clash = self.clash.lock(); (clash.patch_config(patch)?, clash.info.port.clone()) }; // todo: port check - if changed { + if changed_port { let mut service = self.service.lock(); service.restart()?; drop(service); @@ -189,6 +190,10 @@ impl Core { sysopt.init_sysproxy(port, &verge); } + if changed_mode { + self.update_systray(app_handle)?; + } + Ok(()) } @@ -259,12 +264,29 @@ impl Core { /// update the system tray state pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> { + let clash = self.clash.lock(); + let info = clash.info.clone(); + let mode = info.mode.as_ref(); + let verge = self.verge.lock(); let tray = app_handle.tray_handle(); let system_proxy = verge.enable_system_proxy.as_ref(); let tun_mode = verge.enable_tun_mode.as_ref(); + tray + .get_item("rule_mode") + .set_selected((*mode.unwrap()).eq("rule"))?; + tray + .get_item("global_mode") + .set_selected((*mode.unwrap()).eq("global"))?; + tray + .get_item("direct_mode") + .set_selected((*mode.unwrap()).eq("direct"))?; + tray + .get_item("script_mode") + .set_selected((*mode.unwrap()).eq("script"))?; + tray .get_item("system_proxy") .set_selected(*system_proxy.unwrap_or(&false))?; @@ -403,6 +425,31 @@ impl Core { Ok(()) } + + // update rule/global/direct/script mode + pub fn update_mode(&self, app_handle: &AppHandle, mode: &str) -> Result<()> { + let mut mapping = Mapping::new(); + mapping.insert(Value::from("mode"), Value::from(mode)); + + self.patch_clash(mapping, app_handle); + + let (config, info) = { + let clash = self.clash.lock(); + let config = clash.config.clone(); + let info = clash.info.clone(); + (config, info) + }; + + let notice = { + let window = self.window.lock(); + Notice::from(window.clone()) + }; + + let service = self.service.lock(); + service.patch_config(info, config, notice); + + Ok(()) + } } impl Core { diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 8dbd271..8a9c1ba 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -1,3 +1,4 @@ +use super::Clash; use super::{notice::Notice, ClashInfo}; use crate::log_if_err; use crate::utils::{config, dirs}; @@ -189,6 +190,61 @@ impl Service { Ok(()) } + + /// patch clash config + pub fn patch_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> { + if !self.service_mode && self.sidecar.is_none() { + bail!("did not start sidecar"); + } + + if info.server.is_none() { + if info.port.is_none() { + bail!("failed to parse config.yaml file"); + } else { + bail!("failed to parse the server"); + } + } + + let server = info.server.unwrap(); + let server = format!("http://{server}/configs"); + + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", "application/json".parse().unwrap()); + + if let Some(secret) = info.secret.as_ref() { + let secret = format!("Bearer {}", secret.clone()).parse().unwrap(); + headers.insert("Authorization", secret); + } + + tauri::async_runtime::spawn(async move { + // retry 5 times + for _ in 0..5 { + match reqwest::ClientBuilder::new().no_proxy().build() { + Ok(client) => { + let builder = client.patch(&server).headers(headers.clone()).json(&config); + + match builder.send().await { + Ok(resp) => { + if resp.status() != 204 { + log::error!("failed to activate clash with status \"{}\"", resp.status()); + } + + notice.refresh_clash(); + + // do not retry + break; + } + Err(err) => log::error!("failed to activate for `{err}`"), + } + } + Err(err) => log::error!("failed to activate for `{err}`"), + } + sleep(Duration::from_millis(500)).await; + } + }); + + Ok(()) + } } impl Drop for Service { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 10ae550..d40fffe 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -11,6 +11,7 @@ use crate::{ core::Verge, utils::{resolve, server}, }; +use serde_yaml::{Mapping, Value}; use tauri::{ api, CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, }; @@ -30,6 +31,10 @@ fn main() -> std::io::Result<()> { let tray_menu = SystemTrayMenu::new() .add_item(CustomMenuItem::new("open_window", "Show")) + .add_item(CustomMenuItem::new("rule_mode", "Rule Mode")) + .add_item(CustomMenuItem::new("global_mode", "Global Mode")) + .add_item(CustomMenuItem::new("direct_mode", "Direct Mode")) + .add_item(CustomMenuItem::new("script_mode", "Script Mode")) .add_item(CustomMenuItem::new("system_proxy", "System Proxy")) .add_item(CustomMenuItem::new("tun_mode", "Tun Mode")) .add_item(CustomMenuItem::new("restart_clash", "Restart Clash")) @@ -45,6 +50,22 @@ fn main() -> std::io::Result<()> { "open_window" => { resolve::create_window(app_handle); } + "rule_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "rule")); + } + "global_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "global")); + } + "direct_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "direct")); + } + "script_mode" => { + let core = app_handle.state::(); + crate::log_if_err!(core.update_mode(app_handle, "script")); + } "system_proxy" => { let core = app_handle.state::();