From cdb89ad3e5125dd5ad4855fd413d53a66a3ac724 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Sun, 13 Jul 2025 17:46:18 +0800 Subject: [PATCH] feat: migrate mihomo to use kode-bridge IPC on Windows This commit implements a comprehensive migration from legacy service IPC to the kode-bridge library for Windows IPC communication. Key changes include: Replace service_ipc with kode-bridge IpcManager for all mihomo communications Simplify proxy commands using new caching mechanism with ProxyRequestCache Add Windows named pipe (\.\pipe\mihomo) and Unix socket IPC endpoint configuration Update Tauri permissions and dependencies (dashmap, tauri-plugin-notification) Add IPC logging support and improve error handling Fix Windows IPC path handling in directory utilities This migration enables better cross-platform IPC support and improved performance for mihomo proxy core communication. --- src-tauri/Cargo.lock | 86 +++++++++++++++++++++- src-tauri/Cargo.toml | 4 +- src-tauri/capabilities/desktop.json | 3 +- src-tauri/src/cmd/proxy.rs | 106 +++++++--------------------- src-tauri/src/config/clash.rs | 40 ++++++++++- src-tauri/src/core/tray/mod.rs | 1 - src-tauri/src/ipc/general.rs | 13 +++- src-tauri/src/utils/dirs.rs | 3 +- src-tauri/src/utils/logging.rs | 2 + 9 files changed, 168 insertions(+), 90 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 508589602..788246f64 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -375,6 +375,25 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +dependencies = [ + "async-channel 2.5.0", + "async-io 2.4.1", + "async-lock 3.4.0", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.0", + "futures-lite 2.6.0", + "rustix 1.0.7", + "tracing", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -1106,6 +1125,7 @@ dependencies = [ "boa_engine", "chrono", "criterion", + "dashmap 6.1.0", "deelevate", "delay_timer", "dirs 6.0.0", @@ -1149,6 +1169,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-fs", "tauri-plugin-global-shortcut", + "tauri-plugin-notification", "tauri-plugin-process", "tauri-plugin-shell", "tauri-plugin-updater", @@ -3980,6 +4001,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-notification-sys" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119c8490084af61b44c9eda9d626475847a186737c0378c85e32d77c33a01cd4" +dependencies = [ + "cc", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "time", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -4331,6 +4364,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "notify-rust" +version = "4.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400" +dependencies = [ + "futures-lite 2.6.0", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -6670,7 +6717,7 @@ dependencies = [ "async-io 1.13.0", "async-lock 2.8.0", "async-net", - "async-process", + "async-process 1.8.1", "blocking", "futures-lite 1.13.0", ] @@ -7272,6 +7319,25 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "tauri-plugin-notification" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe06ed89cff6d0ec06ff4f544fb961e4718348a33309f56ccb2086e77bc9116" +dependencies = [ + "log", + "notify-rust", + "rand 0.8.5", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "time", + "url", +] + [[package]] name = "tauri-plugin-process" version = "2.3.0" @@ -7449,6 +7515,18 @@ dependencies = [ "toml 0.8.23", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" +dependencies = [ + "quick-xml 0.37.5", + "thiserror 2.0.12", + "windows 0.61.3", + "windows-version", +] + [[package]] name = "tempfile" version = "3.20.0" @@ -9593,8 +9671,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" dependencies = [ "async-broadcast", + "async-executor", + "async-io 2.4.1", + "async-lock 3.4.0", + "async-process 2.3.1", "async-recursion", + "async-task", "async-trait", + "blocking", "enumflags2", "event-listener 5.3.0", "futures-core", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 830b14a4c..40cd37977 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -79,6 +79,8 @@ sha2 = "0.10.9" hex = "0.4.3" scopeguard = "1.2.0" kode-bridge = { git = "https://github.com/KodeBarinn/kode-bridge", branch = "dev" } +dashmap = "6.1.0" +tauri-plugin-notification = "2.3.0" [target.'cfg(windows)'.dependencies] runas = "=1.2.0" @@ -140,4 +142,4 @@ crate-type = ["staticlib", "cdylib", "rlib"] [dev-dependencies] criterion = "0.6.0" -tempfile = "3.20.0" \ No newline at end of file +tempfile = "3.20.0" diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index 823c9af9e..a61c708af 100755 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -17,6 +17,7 @@ "autostart:allow-enable", "autostart:allow-disable", "autostart:allow-is-enabled", - "core:window:allow-set-theme" + "core:window:allow-set-theme", + "notification:default" ] } diff --git a/src-tauri/src/cmd/proxy.rs b/src-tauri/src/cmd/proxy.rs index 6034f2182..f34738b38 100644 --- a/src-tauri/src/cmd/proxy.rs +++ b/src-tauri/src/cmd/proxy.rs @@ -1,99 +1,41 @@ use super::CmdResult; -use crate::{core::handle, module::mihomo::MihomoManager, state::proxy::CmdProxyState}; -use std::{ - sync::Mutex, - time::{Duration, Instant}, -}; -use tauri::Manager; +use crate::{ipc::IpcManager, state::proxy::ProxyRequestCache}; +use std::time::Duration; const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(60); const PROVIDERS_REFRESH_INTERVAL: Duration = Duration::from_secs(60); #[tauri::command] pub async fn get_proxies() -> CmdResult { - let manager = MihomoManager::global(); - - let app_handle = handle::Handle::global().app_handle().unwrap(); - let cmd_proxy_state = app_handle.state::>(); - - let should_refresh = { - let mut state = cmd_proxy_state.lock().unwrap(); - let now = Instant::now(); - if now.duration_since(state.last_refresh_time) > PROXIES_REFRESH_INTERVAL { - state.need_refresh = true; - state.last_refresh_time = now; - } - state.need_refresh - }; - - if should_refresh { - let proxies = manager.get_refresh_proxies().await?; - { - let mut state = cmd_proxy_state.lock().unwrap(); - state.proxies = Box::new(proxies); - state.need_refresh = false; - } - log::debug!(target: "app", "proxies刷新成功"); - } - - let proxies = { - let state = cmd_proxy_state.lock().unwrap(); - state.proxies.clone() - }; - Ok(*proxies) + let manager = IpcManager::global(); + let cache = ProxyRequestCache::global(); + let key = ProxyRequestCache::make_key("proxies", "default"); + let value = cache + .get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async { + manager.get_refresh_proxies().await.expect("fetch failed") + }) + .await; + Ok((*value).clone()) } /// 强制刷新代理缓存用于profile切换 #[tauri::command] pub async fn force_refresh_proxies() -> CmdResult { - let manager = MihomoManager::global(); - let app_handle = handle::Handle::global().app_handle().unwrap(); - let cmd_proxy_state = app_handle.state::>(); - - log::debug!(target: "app", "强制刷新代理缓存"); - - let proxies = manager.get_refresh_proxies().await?; - - { - let mut state = cmd_proxy_state.lock().unwrap(); - state.proxies = Box::new(proxies.clone()); - state.need_refresh = false; - state.last_refresh_time = Instant::now(); - } - - log::debug!(target: "app", "强制刷新代理缓存完成"); - Ok(proxies) + let cache = ProxyRequestCache::global(); + let key = ProxyRequestCache::make_key("proxies", "default"); + cache.map.remove(&key); + get_proxies().await } #[tauri::command] pub async fn get_providers_proxies() -> CmdResult { - let app_handle = handle::Handle::global().app_handle().unwrap(); - let cmd_proxy_state = app_handle.state::>(); - - let should_refresh = { - let mut state = cmd_proxy_state.lock().unwrap(); - let now = Instant::now(); - if now.duration_since(state.last_refresh_time) > PROVIDERS_REFRESH_INTERVAL { - state.need_refresh = true; - state.last_refresh_time = now; - } - state.need_refresh - }; - - if should_refresh { - let manager = MihomoManager::global(); - let providers = manager.get_providers_proxies().await?; - { - let mut state = cmd_proxy_state.lock().unwrap(); - state.providers_proxies = Box::new(providers); - state.need_refresh = false; - } - log::debug!(target: "app", "providers_proxies刷新成功"); - } - - let providers_proxies = { - let state = cmd_proxy_state.lock().unwrap(); - state.providers_proxies.clone() - }; - Ok(*providers_proxies) + let manager = IpcManager::global(); + let cache = ProxyRequestCache::global(); + let key = ProxyRequestCache::make_key("providers", "default"); + let value = cache + .get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async { + manager.get_providers_proxies().await.expect("fetch failed") + }) + .await; + Ok((*value).clone()) } diff --git a/src-tauri/src/config/clash.rs b/src-tauri/src/config/clash.rs index df3eec24f..001139773 100644 --- a/src-tauri/src/config/clash.rs +++ b/src-tauri/src/config/clash.rs @@ -57,6 +57,10 @@ impl IClashTemp { map.insert("ipv6".into(), true.into()); map.insert("mode".into(), "rule".into()); map.insert("external-controller".into(), "127.0.0.1:9097".into()); + #[cfg(unix)] + map.insert("external-controller-unix".into(), "mihomo.sock".into()); + #[cfg(windows)] + map.insert("external-controller-pipe".into(), r"\\.\pipe\mihomo".into()); cors_map.insert("allow-private-network".into(), true.into()); cors_map.insert( "allow-origins".into(), @@ -88,6 +92,11 @@ impl IClashTemp { let socks_port = Self::guard_socks_port(&config); let port = Self::guard_port(&config); let ctrl = Self::guard_server_ctrl(&config); + #[cfg(unix)] + let external_controller_unix = Self::guard_external_controller_unix(&config); + #[cfg(windows)] + let external_controller_pipe = Self::guard_external_controller_pipe(&config); + #[cfg(not(target_os = "windows"))] config.insert("redir-port".into(), redir_port.into()); #[cfg(target_os = "linux")] @@ -96,7 +105,16 @@ impl IClashTemp { config.insert("socks-port".into(), socks_port.into()); config.insert("port".into(), port.into()); config.insert("external-controller".into(), ctrl.into()); - + #[cfg(unix)] + config.insert( + "external-controller-unix".into(), + external_controller_unix.into(), + ); + #[cfg(windows)] + config.insert( + "external-controller-pipe".into(), + external_controller_pipe.into(), + ); config } @@ -257,6 +275,26 @@ impl IClashTemp { Err(_) => "127.0.0.1:9097".into(), } } + + #[cfg(unix)] + pub fn guard_external_controller_unix(config: &Mapping) -> String { + config + .get("external-controller-unix") + .and_then(|value| value.as_str()) + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| "mihomo.sock".to_string()) + } + + #[cfg(windows)] + pub fn guard_external_controller_pipe(config: &Mapping) -> String { + config + .get("external-controller-pipe") + .and_then(|value| value.as_str()) + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| r"\\.\pipe\mihomo".to_string()) + } } #[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 21cc20cd7..e7b1c2f78 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -2,7 +2,6 @@ use once_cell::sync::OnceCell; use tauri::tray::TrayIconBuilder; #[cfg(target_os = "macos")] pub mod speed_rate; -#[cfg(target_os = "macos")] use crate::ipc::Rate; use crate::{ cmd, diff --git a/src-tauri/src/ipc/general.rs b/src-tauri/src/ipc/general.rs index 72a052965..025372c93 100644 --- a/src-tauri/src/ipc/general.rs +++ b/src-tauri/src/ipc/general.rs @@ -6,7 +6,10 @@ use kode_bridge::{ use serde_json::json; use std::sync::OnceLock; -use crate::utils::dirs::ipc_path; +use crate::{ + logging, + utils::{dirs::ipc_path, logging::Type}, +}; pub struct IpcManager { ipc_path: String, @@ -26,6 +29,13 @@ impl IpcManager { "IpcManager initialized with IPC path: {}", instance.ipc_path ); + logging!( + info, + Type::IPC, + true, + "IpcManager initialized with IPC path: {}", + instance.ipc_path + ); instance }) } @@ -91,6 +101,7 @@ impl IpcManager { } impl IpcManager { + #[allow(dead_code)] pub async fn is_mihomo_running(&self) -> Result<(), AnyError> { let url = "/version"; let _response = self.send_request("GET", url, None).await?; diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index 372b2c4f5..3ee716124 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -257,6 +257,5 @@ pub fn ipc_path() -> Result { #[cfg(target_os = "windows")] pub fn ipc_path() -> Result { - let res_dir = app_home_dir()?; - Ok(res_dir.join(r"\\.\pipe\mihomo")) + Ok(PathBuf::from(r"\\.\pipe\mihomo")) } diff --git a/src-tauri/src/utils/logging.rs b/src-tauri/src/utils/logging.rs index ec07ef9f3..7fd3762a7 100644 --- a/src-tauri/src/utils/logging.rs +++ b/src-tauri/src/utils/logging.rs @@ -17,6 +17,7 @@ pub enum Type { Lightweight, Network, ProxyMode, + IPC, } impl fmt::Display for Type { @@ -37,6 +38,7 @@ impl fmt::Display for Type { Type::Lightweight => write!(f, "[Lightweight]"), Type::Network => write!(f, "[Network]"), Type::ProxyMode => write!(f, "[ProxMode]"), + Type::IPC => write!(f, "[IPC]"), } } }