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 50858960..788246f6 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 830b14a4..40cd3797 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 823c9af9..a61c708a 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 6034f218..f34738b3 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 df3eec24..00113977 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 21cc20cd..e7b1c2f7 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 72a05296..025372c9 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 372b2c4f..3ee71612 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 ec07ef9f..7fd3762a 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]"), } } }