From fbb17a0ba5d10bc9bd144344fe2e3e5cd1613ee9 Mon Sep 17 00:00:00 2001
From: limsanity <798607646@qq.com>
Date: Wed, 13 Jul 2022 00:43:27 +0800
Subject: [PATCH] feat(system tray): support switch rule/global/direct/script
 mode in system tray

---
 src-tauri/src/cmds.rs         | 280 ++++++-------
 src-tauri/src/core/clash.rs   | 503 ++++++++++++-----------
 src-tauri/src/core/mod.rs     | 709 +++++++++++++++++---------------
 src-tauri/src/core/notice.rs  |  38 +-
 src-tauri/src/core/service.rs | 746 ++++++++++++++++++----------------
 src-tauri/src/main.rs         | 319 ++++++++-------
 6 files changed, 1371 insertions(+), 1224 deletions(-)

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