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.
This commit is contained in:
Tunglies
2025-07-13 17:46:18 +08:00
Unverified
parent 618c5cb347
commit cdb89ad3e5
9 changed files with 168 additions and 90 deletions

86
src-tauri/Cargo.lock generated
View File

@@ -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",

View File

@@ -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"
tempfile = "3.20.0"

View File

@@ -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"
]
}

View File

@@ -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<serde_json::Value> {
let manager = MihomoManager::global();
let app_handle = handle::Handle::global().app_handle().unwrap();
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
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<serde_json::Value> {
let manager = MihomoManager::global();
let app_handle = handle::Handle::global().app_handle().unwrap();
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
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<serde_json::Value> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
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())
}

View File

@@ -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)]

View File

@@ -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,

View File

@@ -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?;

View File

@@ -257,6 +257,5 @@ pub fn ipc_path() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
pub fn ipc_path() -> Result<PathBuf> {
let res_dir = app_home_dir()?;
Ok(res_dir.join(r"\\.\pipe\mihomo"))
Ok(PathBuf::from(r"\\.\pipe\mihomo"))
}

View File

@@ -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]"),
}
}
}