diff --git a/package.json b/package.json index ce79d0b3..f8d67a18 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:fast": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build -- --profile fast-release", "tauri": "tauri", "web:dev": "vite", - "web:build": "tsc --noEmit && vite build", + "web:build": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tsc --noEmit && vite build", "web:serve": "vite preview", "prebuild": "node scripts/prebuild.mjs", "updater": "node scripts/updater.mjs", @@ -71,7 +71,8 @@ "react-virtuoso": "^4.14.0", "swr": "^2.3.6", "types-pac": "^1.0.3", - "zustand": "^5.0.8" + "zustand": "^5.0.8", + "tauri-plugin-mihomo-api": "file:crates/tauri-plugin-mihomo" }, "devDependencies": { "@actions/github": "^6.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f6ffad9..4f4b9d6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,6 +128,9 @@ importers: swr: specifier: ^2.3.6 version: 2.3.6(react@19.1.1) + tauri-plugin-mihomo-api: + specifier: file:crates/tauri-plugin-mihomo + version: file:crates/tauri-plugin-mihomo types-pac: specifier: ^1.0.3 version: 1.0.3 @@ -3786,6 +3789,9 @@ packages: resolution: {integrity: sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==} engines: {node: '>=18'} + tauri-plugin-mihomo-api@file:crates/tauri-plugin-mihomo: + resolution: {directory: crates/tauri-plugin-mihomo, type: directory} + terser@5.44.0: resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} engines: {node: '>=10'} @@ -8244,6 +8250,10 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + tauri-plugin-mihomo-api@file:crates/tauri-plugin-mihomo: + dependencies: + '@tauri-apps/api': 2.8.0 + terser@5.44.0: dependencies: '@jridgewell/source-map': 0.3.6 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4e143907..2d3d13b6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1147,6 +1147,7 @@ dependencies = [ "tauri-plugin-fs", "tauri-plugin-global-shortcut", "tauri-plugin-http", + "tauri-plugin-mihomo", "tauri-plugin-notification", "tauri-plugin-process", "tauri-plugin-shell", @@ -1654,6 +1655,12 @@ dependencies = [ "parking_lot_core 0.9.11", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "data-url" version = "0.3.2" @@ -7303,6 +7310,30 @@ dependencies = [ "urlpattern", ] +[[package]] +name = "tauri-plugin-mihomo" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "futures-util", + "http 1.3.1", + "httparse", + "log", + "pin-project", + "rand 0.9.2", + "reqwest", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.16", + "tokio", + "tokio-tungstenite", + "ts-rs", + "urlencoding", + "windows-sys 0.59.0", +] + [[package]] name = "tauri-plugin-notification" version = "2.3.1" @@ -7537,6 +7568,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminfo" version = "0.7.5" @@ -7821,6 +7861,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -8215,6 +8267,45 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ts-rs" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be" +dependencies = [ + "thiserror 2.0.16", + "ts-rs-macros", +] + +[[package]] +name = "ts-rs-macros" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "termcolor", +] + +[[package]] +name = "tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" +dependencies = [ + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.16", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" @@ -8337,6 +8428,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 18237f94..1caa0526 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -86,6 +86,7 @@ cfg-if = "1.0.3" nu-ansi-term = { version = "0.50.1", optional = true } console-subscriber = { version = "0.4.1", optional = true } tauri-plugin-devtools = { version = "2.0.1" } +tauri-plugin-mihomo = {path = "../crates/tauri-plugin-mihomo"} [target.'cfg(windows)'.dependencies] diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index 7a09c952..180d5cef 100755 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -24,6 +24,7 @@ { "identifier": "http:default", "allow": [{ "url": "https://*/*" }, { "url": "http://*/*" }] - } + }, + "mihomo:default" ] } diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 0081f917..61ef253d 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -1,21 +1,15 @@ +use std::collections::VecDeque; + use super::CmdResult; use crate::{ - cache::CacheProxy, config::Config, - core::{CoreManager, handle}, -}; -use crate::{ - config::*, - feat, - ipc::{self, IpcManager}, - logging, - utils::logging::Type, - wrap_err, + core::{self, CoreManager, RunningMode, handle, logger}, }; +use crate::{config::*, feat, logging, utils::logging::Type, wrap_err}; use serde_yaml_ng::Mapping; -use std::time::Duration; +// use std::time::Duration; -const CONFIG_REFRESH_INTERVAL: Duration = Duration::from_secs(60); +// const CONFIG_REFRESH_INTERVAL: Duration = Duration::from_secs(60); /// 复制Clash环境变量 #[tauri::command] @@ -113,18 +107,18 @@ pub async fn restart_core() -> CmdResult { } /// 获取代理延迟 -#[tauri::command] -pub async fn clash_api_get_proxy_delay( - name: String, - url: Option, - timeout: i32, -) -> CmdResult { - wrap_err!( - IpcManager::global() - .test_proxy_delay(&name, url, timeout) - .await - ) -} +// #[tauri::command] +// pub async fn clash_api_get_proxy_delay( +// name: String, +// url: Option, +// timeout: i32, +// ) -> CmdResult { +// wrap_err!( +// IpcManager::global() +// .test_proxy_delay(&name, url, timeout) +// .await +// ) +// } /// 测试URL延迟 #[tauri::command] @@ -307,317 +301,328 @@ pub async fn validate_dns_config() -> CmdResult<(bool, String)> { } } -/// 获取Clash版本信息 -#[tauri::command] -pub async fn get_clash_version() -> CmdResult { - wrap_err!(IpcManager::global().get_version().await) -} +// 获取Clash版本信息 +// #[tauri::command] +// pub async fn get_clash_version() -> CmdResult { +// wrap_err!(IpcManager::global().get_version().await) +// } -/// 获取Clash配置 -#[tauri::command] -pub async fn get_clash_config() -> CmdResult { - let manager = IpcManager::global(); - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("clash_config", "default"); - let value = cache - .get_or_fetch(key, CONFIG_REFRESH_INTERVAL, || async { - manager.get_config().await.unwrap_or_else(|e| { - logging!(error, Type::Cmd, "Failed to fetch clash config: {e}"); - serde_json::Value::Object(serde_json::Map::new()) - }) - }) - .await; - Ok((*value).clone()) -} +// 获取Clash配置 +// #[tauri::command] +// pub async fn get_clash_config() -> CmdResult { +// let manager = IpcManager::global(); +// let cache = CacheProxy::global(); +// let key = CacheProxy::make_key("clash_config", "default"); +// let value = cache +// .get_or_fetch(key, CONFIG_REFRESH_INTERVAL, || async { +// manager.get_config().await.unwrap_or_else(|e| { +// logging!(error, Type::Cmd, "Failed to fetch clash config: {e}"); +// serde_json::Value::Object(serde_json::Map::new()) +// }) +// }) +// .await; +// Ok((*value).clone()) +// } -/// 强制刷新Clash配置缓存 -#[tauri::command] -pub async fn force_refresh_clash_config() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("clash_config", "default"); - cache.map.remove(&key); - get_clash_config().await -} +// 强制刷新Clash配置缓存 +// #[tauri::command] +// pub async fn force_refresh_clash_config() -> CmdResult { +// let cache = CacheProxy::global(); +// let key = CacheProxy::make_key("clash_config", "default"); +// cache.map.remove(&key); +// get_clash_config().await +// } -/// 更新地理数据 -#[tauri::command] -pub async fn update_geo_data() -> CmdResult { - wrap_err!(IpcManager::global().update_geo_data().await) -} +// 更新地理数据 +// #[tauri::command] +// pub async fn update_geo_data() -> CmdResult { +// wrap_err!(IpcManager::global().update_geo_data().await) +// } -/// 升级Clash核心 -#[tauri::command] -pub async fn upgrade_clash_core() -> CmdResult { - wrap_err!(IpcManager::global().upgrade_core().await) -} +// 升级Clash核心 +// #[tauri::command] +// pub async fn upgrade_clash_core() -> CmdResult { +// wrap_err!(IpcManager::global().upgrade_core().await) +// } -/// 获取规则 -#[tauri::command] -pub async fn get_clash_rules() -> CmdResult { - wrap_err!(IpcManager::global().get_rules().await) -} +// 获取规则 +// #[tauri::command] +// pub async fn get_clash_rules() -> CmdResult { +// wrap_err!(IpcManager::global().get_rules().await) +// } -/// 更新代理选择 -#[tauri::command] -pub async fn update_proxy_choice(group: String, proxy: String) -> CmdResult { - wrap_err!(IpcManager::global().update_proxy(&group, &proxy).await) -} +// 更新代理选择 +// #[tauri::command] +// pub async fn update_proxy_choice(group: String, proxy: String) -> CmdResult { +// wrap_err!(IpcManager::global().update_proxy(&group, &proxy).await) +// } -/// 获取代理提供者 -#[tauri::command] -pub async fn get_proxy_providers() -> CmdResult { - wrap_err!(IpcManager::global().get_providers_proxies().await) -} +// 获取代理提供者 +// #[tauri::command] +// pub async fn get_proxy_providers() -> CmdResult { +// wrap_err!(IpcManager::global().get_providers_proxies().await) +// } -/// 获取规则提供者 -#[tauri::command] -pub async fn get_rule_providers() -> CmdResult { - wrap_err!(IpcManager::global().get_rule_providers().await) -} +// 获取规则提供者 +// #[tauri::command] +// pub async fn get_rule_providers() -> CmdResult { +// wrap_err!(IpcManager::global().get_rule_providers().await) +// } -/// 代理提供者健康检查 -#[tauri::command] -pub async fn proxy_provider_health_check(name: String) -> CmdResult { - wrap_err!( - IpcManager::global() - .proxy_provider_health_check(&name) - .await - ) -} +// 代理提供者健康检查 +// #[tauri::command] +// pub async fn proxy_provider_health_check(name: String) -> CmdResult { +// wrap_err!( +// IpcManager::global() +// .proxy_provider_health_check(&name) +// .await +// ) +// } -/// 更新代理提供者 -#[tauri::command] -pub async fn update_proxy_provider(name: String) -> CmdResult { - wrap_err!(IpcManager::global().update_proxy_provider(&name).await) -} +// 更新代理提供者 +// #[tauri::command] +// pub async fn update_proxy_provider(name: String) -> CmdResult { +// wrap_err!(IpcManager::global().update_proxy_provider(&name).await) +// } -/// 更新规则提供者 -#[tauri::command] -pub async fn update_rule_provider(name: String) -> CmdResult { - wrap_err!(IpcManager::global().update_rule_provider(&name).await) -} +// 更新规则提供者 +// #[tauri::command] +// pub async fn update_rule_provider(name: String) -> CmdResult { +// wrap_err!(IpcManager::global().update_rule_provider(&name).await) +// } -/// 获取连接 -#[tauri::command] -pub async fn get_clash_connections() -> CmdResult { - wrap_err!(IpcManager::global().get_connections().await) -} +// 获取连接 +// #[tauri::command] +// pub async fn get_clash_connections() -> CmdResult { +// wrap_err!(IpcManager::global().get_connections().await) +// } -/// 删除连接 -#[tauri::command] -pub async fn delete_clash_connection(id: String) -> CmdResult { - wrap_err!(IpcManager::global().delete_connection(&id).await) -} +// 删除连接 +// #[tauri::command] +// pub async fn delete_clash_connection(id: String) -> CmdResult { +// wrap_err!(IpcManager::global().delete_connection(&id).await) +// } -/// 关闭所有连接 -#[tauri::command] -pub async fn close_all_clash_connections() -> CmdResult { - wrap_err!(IpcManager::global().close_all_connections().await) -} +// 关闭所有连接 +// #[tauri::command] +// pub async fn close_all_clash_connections() -> CmdResult { +// wrap_err!(IpcManager::global().close_all_connections().await) +// } -/// 获取流量数据 (使用新的IPC流式监控) -#[tauri::command] -pub async fn get_traffic_data() -> CmdResult { - let traffic = crate::ipc::get_current_traffic().await; - let result = serde_json::json!({ - "up": traffic.total_up, - "down": traffic.total_down, - "up_rate": traffic.up_rate, - "down_rate": traffic.down_rate, - "last_updated": traffic.last_updated.elapsed().as_secs() - }); - Ok(result) -} +// 获取流量数据 (使用新的IPC流式监控) +// #[tauri::command] +// pub async fn get_traffic_data() -> CmdResult { +// let traffic = crate::ipc::get_current_traffic().await; +// let result = serde_json::json!({ +// "up": traffic.total_up, +// "down": traffic.total_down, +// "up_rate": traffic.up_rate, +// "down_rate": traffic.down_rate, +// "last_updated": traffic.last_updated.elapsed().as_secs() +// }); +// Ok(result) +// } + +// 获取内存数据 (使用新的IPC流式监控) +// #[tauri::command] +// pub async fn get_memory_data() -> CmdResult { +// let memory = crate::ipc::get_current_memory().await; +// let usage_percent = if memory.oslimit > 0 { +// (memory.inuse as f64 / memory.oslimit as f64) * 100.0 +// } else { +// 0.0 +// }; +// let result = serde_json::json!({ +// "inuse": memory.inuse, +// "oslimit": memory.oslimit, +// "usage_percent": usage_percent, +// "last_updated": memory.last_updated.elapsed().as_secs() +// }); +// Ok(result) +// } + +// 启动流量监控服务 (IPC流式监控自动启动,此函数为兼容性保留) +// #[tauri::command] +// pub async fn start_traffic_service() -> CmdResult { +// logging!(trace, Type::Ipc, "启动流量监控服务 (IPC流式监控)"); +// // 新的IPC监控在首次访问时自动启动 +// // 触发一次访问以确保监控器已初始化 +// let _ = crate::ipc::get_current_traffic().await; +// let _ = crate::ipc::get_current_memory().await; +// logging!(info, Type::Ipc, "IPC流式监控已激活"); +// Ok(()) +// } + +// 停止流量监控服务 (IPC流式监控无需显式停止,此函数为兼容性保留) +// #[tauri::command] +// pub async fn stop_traffic_service() -> CmdResult { +// logging!(trace, Type::Ipc, "停止流量监控服务请求 (IPC流式监控)"); +// // 新的IPC监控是持久的,无需显式停止 +// logging!(info, Type::Ipc, "IPC流式监控继续运行"); +// Ok(()) +// } + +// 获取格式化的流量数据 (包含单位,便于前端显示) +// #[tauri::command] +// pub async fn get_formatted_traffic_data() -> CmdResult { +// logging!(trace, Type::Ipc, "获取格式化流量数据"); +// let (up_rate, down_rate, total_up, total_down, is_fresh) = +// crate::ipc::get_formatted_traffic().await; +// let result = serde_json::json!({ +// "up_rate_formatted": up_rate, +// "down_rate_formatted": down_rate, +// "total_up_formatted": total_up, +// "total_down_formatted": total_down, +// "is_fresh": is_fresh +// }); +// logging!( +// debug, +// Type::Ipc, +// "格式化流量数据: ↑{up_rate}/s ↓{down_rate}/s (总计: ↑{total_up} ↓{total_down})" +// ); +// Ok(result) +// } + +// 获取格式化的内存数据 (包含单位,便于前端显示) +// #[tauri::command] +// pub async fn get_formatted_memory_data() -> CmdResult { +// logging!(info, Type::Ipc, "获取格式化内存数据"); +// let (inuse, oslimit, usage_percent, is_fresh) = crate::ipc::get_formatted_memory().await; +// let result = serde_json::json!({ +// "inuse_formatted": inuse, +// "oslimit_formatted": oslimit, +// "usage_percent": usage_percent, +// "is_fresh": is_fresh +// }); +// logging!( +// debug, +// Type::Ipc, +// "格式化内存数据: {inuse} / {oslimit} ({usage_percent:.1}%)" +// ); +// Ok(result) +// } + +// 获取系统监控概览 (流量+内存,便于前端一次性获取所有状态) +// #[tauri::command] +// pub async fn get_system_monitor_overview() -> CmdResult { +// logging!(debug, Type::Ipc, "获取系统监控概览"); + +// // 并发获取流量和内存数据 +// let (traffic, memory) = tokio::join!( +// crate::ipc::get_current_traffic(), +// crate::ipc::get_current_memory() +// ); + +// let (traffic_formatted, memory_formatted) = tokio::join!( +// crate::ipc::get_formatted_traffic(), +// crate::ipc::get_formatted_memory() +// ); + +// let traffic_is_fresh = traffic.last_updated.elapsed().as_secs() < 5; +// let memory_is_fresh = memory.last_updated.elapsed().as_secs() < 10; + +// let result = serde_json::json!({ +// "traffic": { +// "raw": { +// "up": traffic.total_up, +// "down": traffic.total_down, +// "up_rate": traffic.up_rate, +// "down_rate": traffic.down_rate +// }, +// "formatted": { +// "up_rate": traffic_formatted.0, +// "down_rate": traffic_formatted.1, +// "total_up": traffic_formatted.2, +// "total_down": traffic_formatted.3 +// }, +// "is_fresh": traffic_is_fresh +// }, +// "memory": { +// "raw": { +// "inuse": memory.inuse, +// "oslimit": memory.oslimit, +// "usage_percent": if memory.oslimit > 0 { +// (memory.inuse as f64 / memory.oslimit as f64) * 100.0 +// } else { +// 0.0 +// } +// }, +// "formatted": { +// "inuse": memory_formatted.0, +// "oslimit": memory_formatted.1, +// "usage_percent": memory_formatted.2 +// }, +// "is_fresh": memory_is_fresh +// }, +// "overall_status": if traffic_is_fresh && memory_is_fresh { "healthy" } else { "stale" } +// }); + +// Ok(result) +// } + +// 获取代理组延迟 +// #[tauri::command] +// pub async fn get_group_proxy_delays( +// group_name: String, +// url: Option, +// timeout: Option, +// ) -> CmdResult { +// wrap_err!( +// IpcManager::global() +// .get_group_proxy_delays(&group_name, url, timeout.unwrap_or(10000)) +// .await +// ) +// } + +// 检查调试是否启用 +// #[tauri::command] +// pub async fn is_clash_debug_enabled() -> CmdResult { +// match IpcManager::global().is_debug_enabled().await { +// Ok(enabled) => Ok(enabled), +// Err(_) => Ok(false), +// } +// } + +// 垃圾回收 +// #[tauri::command] +// pub async fn clash_gc() -> CmdResult { +// wrap_err!(IpcManager::global().gc().await) +// } + +// 获取日志 (使用新的流式实现) +// #[tauri::command] +// pub async fn get_clash_logs() -> CmdResult { +// Ok(ipc::get_logs_json().await) +// } -/// 获取内存数据 (使用新的IPC流式监控) #[tauri::command] -pub async fn get_memory_data() -> CmdResult { - let memory = crate::ipc::get_current_memory().await; - let usage_percent = if memory.oslimit > 0 { - (memory.inuse as f64 / memory.oslimit as f64) * 100.0 - } else { - 0.0 +pub async fn get_clash_logs() -> CmdResult> { + let logs = match core::CoreManager::global().get_running_mode() { + // TODO: 服务模式下日志获取接口 + RunningMode::Service => VecDeque::new(), + RunningMode::Sidecar => logger::Logger::global().get_logs().clone(), + _ => VecDeque::new(), }; - let result = serde_json::json!({ - "inuse": memory.inuse, - "oslimit": memory.oslimit, - "usage_percent": usage_percent, - "last_updated": memory.last_updated.elapsed().as_secs() - }); - Ok(result) + Ok(logs) } -/// 启动流量监控服务 (IPC流式监控自动启动,此函数为兼容性保留) -#[tauri::command] -pub async fn start_traffic_service() -> CmdResult { - logging!(trace, Type::Ipc, "启动流量监控服务 (IPC流式监控)"); - // 新的IPC监控在首次访问时自动启动 - // 触发一次访问以确保监控器已初始化 - let _ = crate::ipc::get_current_traffic().await; - let _ = crate::ipc::get_current_memory().await; - logging!(info, Type::Ipc, "IPC流式监控已激活"); - Ok(()) -} +// 启动日志监控 +// #[tauri::command] +// pub async fn start_logs_monitoring(level: Option) -> CmdResult { +// ipc::start_logs_monitoring(level).await; +// Ok(()) +// } -/// 停止流量监控服务 (IPC流式监控无需显式停止,此函数为兼容性保留) -#[tauri::command] -pub async fn stop_traffic_service() -> CmdResult { - logging!(trace, Type::Ipc, "停止流量监控服务请求 (IPC流式监控)"); - // 新的IPC监控是持久的,无需显式停止 - logging!(info, Type::Ipc, "IPC流式监控继续运行"); - Ok(()) -} +// 停止日志监控 +// #[tauri::command] +// pub async fn stop_logs_monitoring() -> CmdResult { +// ipc::stop_logs_monitoring().await; +// Ok(()) +// } -/// 获取格式化的流量数据 (包含单位,便于前端显示) -#[tauri::command] -pub async fn get_formatted_traffic_data() -> CmdResult { - logging!(trace, Type::Ipc, "获取格式化流量数据"); - let (up_rate, down_rate, total_up, total_down, is_fresh) = - crate::ipc::get_formatted_traffic().await; - let result = serde_json::json!({ - "up_rate_formatted": up_rate, - "down_rate_formatted": down_rate, - "total_up_formatted": total_up, - "total_down_formatted": total_down, - "is_fresh": is_fresh - }); - logging!( - debug, - Type::Ipc, - "格式化流量数据: ↑{up_rate}/s ↓{down_rate}/s (总计: ↑{total_up} ↓{total_down})" - ); - Ok(result) -} - -/// 获取格式化的内存数据 (包含单位,便于前端显示) -#[tauri::command] -pub async fn get_formatted_memory_data() -> CmdResult { - logging!(info, Type::Ipc, "获取格式化内存数据"); - let (inuse, oslimit, usage_percent, is_fresh) = crate::ipc::get_formatted_memory().await; - let result = serde_json::json!({ - "inuse_formatted": inuse, - "oslimit_formatted": oslimit, - "usage_percent": usage_percent, - "is_fresh": is_fresh - }); - logging!( - debug, - Type::Ipc, - "格式化内存数据: {inuse} / {oslimit} ({usage_percent:.1}%)" - ); - Ok(result) -} - -/// 获取系统监控概览 (流量+内存,便于前端一次性获取所有状态) -#[tauri::command] -pub async fn get_system_monitor_overview() -> CmdResult { - logging!(debug, Type::Ipc, "获取系统监控概览"); - - // 并发获取流量和内存数据 - let (traffic, memory) = tokio::join!( - crate::ipc::get_current_traffic(), - crate::ipc::get_current_memory() - ); - - let (traffic_formatted, memory_formatted) = tokio::join!( - crate::ipc::get_formatted_traffic(), - crate::ipc::get_formatted_memory() - ); - - let traffic_is_fresh = traffic.last_updated.elapsed().as_secs() < 5; - let memory_is_fresh = memory.last_updated.elapsed().as_secs() < 10; - - let result = serde_json::json!({ - "traffic": { - "raw": { - "up": traffic.total_up, - "down": traffic.total_down, - "up_rate": traffic.up_rate, - "down_rate": traffic.down_rate - }, - "formatted": { - "up_rate": traffic_formatted.0, - "down_rate": traffic_formatted.1, - "total_up": traffic_formatted.2, - "total_down": traffic_formatted.3 - }, - "is_fresh": traffic_is_fresh - }, - "memory": { - "raw": { - "inuse": memory.inuse, - "oslimit": memory.oslimit, - "usage_percent": if memory.oslimit > 0 { - (memory.inuse as f64 / memory.oslimit as f64) * 100.0 - } else { - 0.0 - } - }, - "formatted": { - "inuse": memory_formatted.0, - "oslimit": memory_formatted.1, - "usage_percent": memory_formatted.2 - }, - "is_fresh": memory_is_fresh - }, - "overall_status": if traffic_is_fresh && memory_is_fresh { "healthy" } else { "stale" } - }); - - Ok(result) -} - -/// 获取代理组延迟 -#[tauri::command] -pub async fn get_group_proxy_delays( - group_name: String, - url: Option, - timeout: Option, -) -> CmdResult { - wrap_err!( - IpcManager::global() - .get_group_proxy_delays(&group_name, url, timeout.unwrap_or(10000)) - .await - ) -} - -/// 检查调试是否启用 -#[tauri::command] -pub async fn is_clash_debug_enabled() -> CmdResult { - match IpcManager::global().is_debug_enabled().await { - Ok(enabled) => Ok(enabled), - Err(_) => Ok(false), - } -} - -/// 垃圾回收 -#[tauri::command] -pub async fn clash_gc() -> CmdResult { - wrap_err!(IpcManager::global().gc().await) -} - -/// 获取日志 (使用新的流式实现) -#[tauri::command] -pub async fn get_clash_logs() -> CmdResult { - Ok(ipc::get_logs_json().await) -} - -/// 启动日志监控 -#[tauri::command] -pub async fn start_logs_monitoring(level: Option) -> CmdResult { - ipc::start_logs_monitoring(level).await; - Ok(()) -} - -/// 停止日志监控 -#[tauri::command] -pub async fn stop_logs_monitoring() -> CmdResult { - ipc::stop_logs_monitoring().await; - Ok(()) -} - -/// 清除日志 -#[tauri::command] -pub async fn clear_logs() -> CmdResult { - ipc::clear_logs().await; - Ok(()) -} +// 清除日志 +// #[tauri::command] +// pub async fn clear_logs() -> CmdResult { +// ipc::clear_logs().await; +// Ok(()) +// } diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index d76dc00b..71ccecaa 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -481,11 +481,11 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { handle::Handle::refresh_clash(); // 强制刷新代理缓存,确保profile切换后立即获取最新节点数据 - crate::process::AsyncHandler::spawn(|| async move { - if let Err(e) = super::proxy::force_refresh_proxies().await { - log::warn!(target: "app", "强制刷新代理缓存失败: {e}"); - } - }); + // crate::process::AsyncHandler::spawn(|| async move { + // if let Err(e) = super::proxy::force_refresh_proxies().await { + // log::warn!(target: "app", "强制刷新代理缓存失败: {e}"); + // } + // }); if let Err(e) = Tray::global().update_tooltip().await { log::warn!(target: "app", "异步更新托盘提示失败: {e}"); diff --git a/src-tauri/src/cmd/proxy.rs b/src-tauri/src/cmd/proxy.rs index 387da3ad..be83c07d 100644 --- a/src-tauri/src/cmd/proxy.rs +++ b/src-tauri/src/cmd/proxy.rs @@ -3,57 +3,60 @@ use tauri::Emitter; use super::CmdResult; use crate::{ cache::CacheProxy, - core::{handle::Handle, tray::Tray}, - ipc::IpcManager, + core::{ + handle::{self, Handle}, + tray::Tray, + }, logging, utils::logging::Type, }; -use std::time::Duration; +// use std::time::Duration; -const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(60); -const PROVIDERS_REFRESH_INTERVAL: Duration = Duration::from_secs(60); +// 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 cache = CacheProxy::global(); - let key = CacheProxy::make_key("proxies", "default"); - let value = cache - .get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async { - let manager = IpcManager::global(); - manager.get_proxies().await.unwrap_or_else(|e| { - logging!(error, Type::Cmd, "Failed to fetch proxies: {e}"); - serde_json::Value::Object(serde_json::Map::new()) - }) - }) - .await; - Ok((*value).clone()) -} +// #[tauri::command] +// pub async fn get_proxies() -> CmdResult { +// let cache = CacheProxy::global(); +// let key = CacheProxy::make_key("proxies", "default"); +// let value = cache +// .get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async { +// let manager = IpcManager::global(); +// manager.get_proxies().await.unwrap_or_else(|e| { +// logging!(error, Type::Cmd, "Failed to fetch proxies: {e}"); +// serde_json::Value::Object(serde_json::Map::new()) +// }) +// }) +// .await; +// Ok((*value).clone()) +// } /// 强制刷新代理缓存用于profile切换 -#[tauri::command] -pub async fn force_refresh_proxies() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("proxies", "default"); - cache.map.remove(&key); - get_proxies().await -} +// #[tauri::command] +// pub async fn force_refresh_proxies() -> CmdResult { +// let cache = CacheProxy::global(); +// let key = CacheProxy::make_key("proxies", "default"); +// cache.map.remove(&key); +// get_proxies().await +// } -#[tauri::command] -pub async fn get_providers_proxies() -> CmdResult { - let cache = CacheProxy::global(); - let key = CacheProxy::make_key("providers", "default"); - let value = cache - .get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async { - let manager = IpcManager::global(); - manager.get_providers_proxies().await.unwrap_or_else(|e| { - logging!(error, Type::Cmd, "Failed to fetch provider proxies: {e}"); - serde_json::Value::Object(serde_json::Map::new()) - }) - }) - .await; - Ok((*value).clone()) -} +// #[tauri::command] +// pub async fn get_providers_proxies() -> CmdResult { +// let cache = CacheProxy::global(); +// let key = CacheProxy::make_key("providers", "default"); +// let value = cache +// .get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async { +// let manager = IpcManager::global(); +// manager.get_providers_proxies().await.unwrap_or_else(|e| { +// logging!(error, Type::Cmd, "Failed to fetch provider proxies: {e}"); +// serde_json::Value::Object(serde_json::Map::new()) +// }) +// }) +// .await; +// Ok((*value).clone()) +// } +// TODO: 前端通过 emit 发送更新事件, tray 监听更新事件 /// 同步托盘和GUI的代理选择状态 #[tauri::command] pub async fn sync_tray_proxy_selection() -> CmdResult<()> { @@ -74,7 +77,11 @@ pub async fn sync_tray_proxy_selection() -> CmdResult<()> { /// 更新代理选择并同步托盘和GUI状态 #[tauri::command] pub async fn update_proxy_and_sync(group: String, proxy: String) -> CmdResult<()> { - match IpcManager::global().update_proxy(&group, &proxy).await { + match handle::Handle::mihomo() + .await + .select_node_for_group(&group, &proxy) + .await + { Ok(_) => { // println!("Proxy updated successfully: {} -> {}", group,proxy); logging!( @@ -93,10 +100,8 @@ pub async fn update_proxy_and_sync(group: String, proxy: String) -> CmdResult<() logging!(error, Type::Cmd, "Failed to sync tray menu: {}", e); } - if let Some(app_handle) = Handle::global().app_handle() { - let _ = app_handle.emit("verge://force-refresh-proxies", ()); - let _ = app_handle.emit("verge://refresh-proxy-config", ()); - } + let app_handle = Handle::app_handle(); + let _ = app_handle.emit("verge://refresh-proxy-config", ()); logging!( info, diff --git a/src-tauri/src/cmd/system.rs b/src-tauri/src/cmd/system.rs index c3d2ab41..2ef6cf72 100644 --- a/src-tauri/src/cmd/system.rs +++ b/src-tauri/src/cmd/system.rs @@ -28,9 +28,7 @@ pub async fn export_diagnostic_info() -> CmdResult<()> { let sysinfo = PlatformSpecification::new_sync(); let info = format!("{sysinfo:?}"); - let app_handle = handle::Handle::global() - .app_handle() - .ok_or("Failed to get app handle")?; + let app_handle = handle::Handle::app_handle(); let cliboard = app_handle.clipboard(); if cliboard.write_text(info).is_err() { logging!(error, Type::System, "Failed to write to clipboard"); diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index ff1e1cc0..a3ef55d6 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,11 +1,11 @@ use crate::AsyncHandler; +use crate::core::logger::Logger; use crate::{ config::*, core::{ handle, service::{self, SERVICE_MANAGER, ServiceStatus}, }, - ipc::IpcManager, logging, logging_error, singleton_lazy, utils::{ dirs, @@ -250,11 +250,7 @@ impl CoreManager { let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); logging!(info, Type::Config, true, "使用内核: {}", clash_core); - let app_handle = handle::Handle::global().app_handle().ok_or_else(|| { - let msg = "Failed to get app handle"; - logging!(error, Type::Core, true, "{}", msg); - anyhow::anyhow!(msg) - })?; + let app_handle = handle::Handle::app_handle(); let app_dir = dirs::app_home_dir()?; let app_dir_str = dirs::path_to_str(&app_dir)?; logging!(info, Type::Config, true, "验证目录: {}", app_dir_str); @@ -414,7 +410,11 @@ impl CoreManager { logging_error!(Type::Core, true, "{}", msg); msg }); - match IpcManager::global().put_configs_force(run_path_str?).await { + match handle::Handle::mihomo() + .await + .reload_config(true, run_path_str?) + .await + { Ok(_) => { Config::runtime().await.apply(); logging!(info, Type::Core, true, "Configuration updated successfully"); @@ -733,9 +733,7 @@ impl CoreManager { logging!(info, Type::Core, true, "Running core by sidecar"); let config_file = &Config::generate_file(ConfigType::Run).await?; - let app_handle = handle::Handle::global() - .app_handle() - .ok_or(anyhow::anyhow!("failed to get app handle"))?; + let app_handle = handle::Handle::app_handle(); let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); let config_dir = dirs::app_home_dir()?; @@ -774,12 +772,16 @@ impl CoreManager { while let Some(event) = rx.recv().await { match event { tauri_plugin_shell::process::CommandEvent::Stdout(line) => { - if let Err(e) = writeln!(log_file, "{}", String::from_utf8_lossy(&line)) { + let line = String::from_utf8_lossy(&line); + Logger::global().append_log(line.to_string()); + if let Err(e) = writeln!(log_file, "{}", line) { eprintln!("[Sidecar] write stdout failed: {e}"); } } tauri_plugin_shell::process::CommandEvent::Stderr(line) => { - let _ = writeln!(log_file, "[stderr] {}", String::from_utf8_lossy(&line)); + let line = String::from_utf8_lossy(&line); + Logger::global().append_log(line.to_string()); + let _ = writeln!(log_file, "[stderr] {}", line); } tauri_plugin_shell::process::CommandEvent::Terminated(term) => { let _ = writeln!(log_file, "[terminated] {:?}", term); @@ -900,6 +902,7 @@ impl CoreManager { /// 停止核心运行 pub async fn stop_core(&self) -> Result<()> { + Logger::global().clear_logs(); match self.get_running_mode() { RunningMode::Service => self.stop_core_by_service().await, RunningMode::Sidecar => self.stop_core_by_sidecar(), diff --git a/src-tauri/src/core/handle.rs b/src-tauri/src/core/handle.rs index c0f4eddb..a85f225b 100644 --- a/src-tauri/src/core/handle.rs +++ b/src-tauri/src/core/handle.rs @@ -1,4 +1,4 @@ -use crate::singleton; +use crate::{APP_HANDLE, singleton}; use parking_lot::RwLock; use std::{ sync::{ @@ -10,6 +10,8 @@ use std::{ time::{Duration, Instant}, }; use tauri::{AppHandle, Emitter, Manager, WebviewWindow}; +use tauri_plugin_mihomo::{Mihomo, MihomoExt}; +use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use crate::{logging, utils::logging::Type}; @@ -107,7 +109,7 @@ impl NotificationSystem { continue; } - if let Some(window) = handle.get_window() { + if let Some(window) = Handle::get_window() { *system.last_emit_time.write() = Instant::now(); let (event_name_str, payload_result) = match event { @@ -249,7 +251,6 @@ impl NotificationSystem { #[derive(Debug, Clone)] pub struct Handle { - pub app_handle: Arc>>, pub is_exiting: Arc>, startup_errors: Arc>>, startup_completed: Arc>, @@ -259,7 +260,6 @@ pub struct Handle { impl Default for Handle { fn default() -> Self { Self { - app_handle: Arc::new(RwLock::new(None)), is_exiting: Arc::new(RwLock::new(false)), startup_errors: Arc::new(RwLock::new(Vec::new())), startup_completed: Arc::new(RwLock::new(false)), @@ -276,12 +276,7 @@ impl Handle { Self::default() } - pub fn init(&self, app_handle: AppHandle) { - { - let mut handle = self.app_handle.write(); - *handle = Some(app_handle); - } - + pub fn init(&self) { let mut system_opt = self.notification_system.write(); if let Some(system) = system_opt.as_mut() { system.start(); @@ -289,12 +284,21 @@ impl Handle { } /// 获取 AppHandle - pub fn app_handle(&self) -> Option { - self.app_handle.read().clone() + pub fn app_handle() -> &'static AppHandle { + APP_HANDLE.get().expect("failed to get global app handle") } - pub fn get_window(&self) -> Option { - let app_handle = self.app_handle()?; + pub async fn mihomo() -> RwLockReadGuard<'static, Mihomo> { + Self::app_handle().mihomo().read().await + } + + #[allow(unused)] + pub async fn mihomo_mut() -> RwLockWriteGuard<'static, Mihomo> { + Self::app_handle().mihomo().write().await + } + + pub fn get_window() -> Option { + let app_handle = Self::app_handle(); let window: Option = app_handle.get_webview_window("main"); if window.is_none() { log::debug!(target:"app", "main window not found"); diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 67258242..6802ac9e 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -200,9 +200,7 @@ impl Hotkey { hotkey: &str, function: HotkeyFunction, ) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; + let app_handle = handle::Handle::app_handle(); let manager = app_handle.global_shortcut(); logging!( @@ -375,9 +373,7 @@ impl Hotkey { } pub fn reset(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; + let app_handle = handle::Handle::app_handle(); let manager = app_handle.global_shortcut(); manager.unregister_all()?; Ok(()) @@ -390,9 +386,7 @@ impl Hotkey { } pub fn unregister(&self, hotkey: &str) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; + let app_handle = handle::Handle::app_handle(); let manager = app_handle.global_shortcut(); manager.unregister(hotkey)?; logging!(debug, Type::Hotkey, "Unregister hotkey {}", hotkey); @@ -468,17 +462,7 @@ impl Hotkey { impl Drop for Hotkey { fn drop(&mut self) { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - logging!( - error, - Type::Hotkey, - "Failed to get app handle during hotkey cleanup" - ); - return; - } - }; + let app_handle = handle::Handle::app_handle(); if let Err(e) = app_handle.global_shortcut().unregister_all() { logging!( error, diff --git a/src-tauri/src/core/logger.rs b/src-tauri/src/core/logger.rs new file mode 100644 index 00000000..7eb30479 --- /dev/null +++ b/src-tauri/src/core/logger.rs @@ -0,0 +1,37 @@ +use std::{collections::VecDeque, sync::Arc}; + +use once_cell::sync::OnceCell; +use parking_lot::{RwLock, RwLockReadGuard}; + +const LOGS_QUEUE_LEN: usize = 100; + +pub struct Logger { + logs: Arc>>, +} + +impl Logger { + pub fn global() -> &'static Logger { + static LOGGER: OnceCell = OnceCell::new(); + + LOGGER.get_or_init(|| Logger { + logs: Arc::new(RwLock::new(VecDeque::with_capacity(LOGS_QUEUE_LEN + 10))), + }) + } + + pub fn get_logs(&self) -> RwLockReadGuard<'_, VecDeque> { + self.logs.read() + } + + pub fn append_log(&self, text: String) { + let mut logs = self.logs.write(); + if logs.len() > LOGS_QUEUE_LEN { + logs.pop_front(); + } + logs.push_back(text); + } + + pub fn clear_logs(&self) { + let mut logs = self.logs.write(); + logs.clear(); + } +} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 044abaf9..5972458b 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -5,6 +5,7 @@ mod core; pub mod event_driven_proxy; pub mod handle; pub mod hotkey; +pub mod logger; pub mod service; pub mod service_ipc; pub mod sysopt; diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 59c886c8..85e53a28 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -293,10 +293,7 @@ impl Sysopt { /// 尝试使用原来的自启动方法 fn try_original_autostart_method(&self, is_enable: bool) { - let Some(app_handle) = Handle::global().app_handle() else { - log::error!(target: "app", "App handle not available for autostart"); - return; - }; + let app_handle = Handle::app_handle(); let autostart_manager = app_handle.autolaunch(); if is_enable { @@ -323,9 +320,7 @@ impl Sysopt { } // 回退到原来的方法 - let app_handle = Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; + let app_handle = Handle::app_handle(); let autostart_manager = app_handle.autolaunch(); match autostart_manager.is_enabled() { diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 3df6a820..909ed19e 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -3,16 +3,13 @@ use tauri::Emitter; use tauri::tray::TrayIconBuilder; #[cfg(target_os = "macos")] pub mod speed_rate; -use crate::ipc::Rate; use crate::module::lightweight; use crate::process::AsyncHandler; use crate::utils::window_manager::WindowManager; use crate::{ Type, cmd, config::Config, - feat, - ipc::IpcManager, - logging, + feat, logging, module::lightweight::is_in_lightweight_mode, singleton_lazy, utils::{dirs::find_target_icons, i18n::t}, @@ -54,7 +51,7 @@ fn should_handle_tray_click() -> bool { *last_click = now; true } else { - log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms", + log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms", now.duration_since(*last_click).as_millis()); false } @@ -189,18 +186,14 @@ singleton_lazy!(Tray, TRAY, Tray::default); impl Tray { pub async fn init(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray initialization"))?; + let app_handle = handle::Handle::app_handle(); self.create_tray_from_handle(&app_handle).await?; Ok(()) } /// 更新托盘点击行为 pub async fn update_click_behavior(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray update"))?; + let app_handle = handle::Handle::app_handle(); let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); let tray = app_handle @@ -240,13 +233,7 @@ impl Tray { return Ok(()); } - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘菜单失败: app_handle不存在"); - return Ok(()); - } - }; + let app_handle = handle::Handle::app_handle(); // 设置更新状态 self.menu_updating.store(true, Ordering::Release); @@ -309,7 +296,7 @@ impl Tray { /// 更新托盘图标 #[cfg(target_os = "macos")] pub async fn update_icon(&self, _rate: Option) -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { + let app_handle = match handle::Handle::app_handle() { Some(handle) => handle, None => { log::warn!(target: "app", "更新托盘图标失败: app_handle不存在"); @@ -345,14 +332,8 @@ impl Tray { } #[cfg(not(target_os = "macos"))] - pub async fn update_icon(&self, _rate: Option) -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘图标失败: app_handle不存在"); - return Ok(()); - } - }; + pub async fn update_icon(&self) -> Result<()> { + let app_handle = handle::Handle::app_handle(); let tray = match app_handle.tray_by_id("main") { Some(tray) => tray, @@ -379,9 +360,7 @@ impl Tray { /// 更新托盘显示状态的函数 pub async fn update_tray_display(&self) -> Result<()> { - let app_handle = handle::Handle::global() - .app_handle() - .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray update"))?; + let app_handle = handle::Handle::app_handle(); let _tray = app_handle .tray_by_id("main") .ok_or_else(|| anyhow::anyhow!("Failed to get main tray"))?; @@ -394,13 +373,7 @@ impl Tray { /// 更新托盘提示 pub async fn update_tooltip(&self) -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "更新托盘提示失败: app_handle不存在"); - return Ok(()); - } - }; + let app_handle = handle::Handle::app_handle(); let verge = Config::verge().await.latest_ref().clone(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); @@ -454,7 +427,7 @@ impl Tray { // self.update_menu().await?; // 更新轻量模式显示状态 self.update_tray_display().await?; - self.update_icon(None).await?; + self.update_icon().await?; self.update_tooltip().await?; Ok(()) } @@ -540,7 +513,7 @@ impl Tray { // 确保所有状态更新完成 self.update_tray_display().await?; // self.update_menu().await?; - self.update_icon(None).await?; + self.update_icon().await?; self.update_tooltip().await?; Ok(()) @@ -568,14 +541,7 @@ async fn create_tray_menu( .unwrap_or_default() }; - let proxy_nodes_data = cmd::get_proxies().await.unwrap_or_else(|e| { - logging!( - error, - Type::Cmd, - "Failed to fetch proxies for tray menu: {e}" - ); - serde_json::Value::Object(serde_json::Map::new()) - }); + let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await?; let version = env!("CARGO_PKG_VERSION"); @@ -628,102 +594,96 @@ async fn create_tray_menu( let mut submenus = Vec::new(); let mut group_name_submenus_hash = HashMap::new(); - if let Some(proxies) = proxy_nodes_data.get("proxies").and_then(|v| v.as_object()) { - for (group_name, group_data) in proxies.iter() { - // Filter groups based on mode - let should_show = match mode { + for (group_name, group_data) in proxy_nodes_data.proxies.iter() { + // Filter groups based on mode + let should_show = match mode { "global" => group_name == "GLOBAL", _ => group_name != "GLOBAL", } && // Check if the group is hidden - !group_data.get("hidden").and_then(|v| v.as_bool()).unwrap_or(false); + !group_data.hidden.unwrap_or_default(); - if !should_show { - continue; + if !should_show { + continue; + } + + let Some(all_proxies) = group_data.all.as_ref() else { + continue; + }; + + let now_proxy = group_data.now.as_deref().unwrap_or_default(); + + // Create proxy items + let group_items: Vec> = all_proxies + .iter() + .filter_map(|proxy_str| { + let is_selected = *proxy_str == now_proxy; + let item_id = format!("proxy_{}_{}", group_name, proxy_str); + + // Get delay for display + let delay_text = proxy_nodes_data + .proxies + .get(proxy_str) + .and_then(|h| h.history.last()) + .map(|h| match h.delay { + 0 => "-ms".to_string(), + delay if delay >= 10000 => "-ms".to_string(), + _ => format!("{}ms", h.delay), + }) + .unwrap_or_else(|| "-ms".to_string()); + + let display_text = format!("{} | {}", proxy_str, delay_text); + + CheckMenuItem::with_id( + app_handle, + item_id, + display_text, + true, + is_selected, + None::<&str>, + ) + .map_err(|e| log::warn!(target: "app", "创建代理菜单项失败: {}", e)) + .ok() + }) + .collect(); + + if group_items.is_empty() { + continue; + } + + // Determine if group is active + let is_group_active = match mode { + "global" => group_name == "GLOBAL" && !now_proxy.is_empty(), + "direct" => false, + _ => { + current_profile_selected + .iter() + .any(|s| s.name.as_deref() == Some(&group_name)) + && !now_proxy.is_empty() } + }; - let Some(all_proxies) = group_data.get("all").and_then(|v| v.as_array()) else { - continue; - }; + let group_display_name = if is_group_active { + format!("✓ {}", group_name) + } else { + group_name.to_string() + }; - let now_proxy = group_data.get("now").and_then(|v| v.as_str()).unwrap_or(""); + let group_items_refs: Vec<&dyn IsMenuItem> = group_items + .iter() + .map(|item| item as &dyn IsMenuItem) + .collect(); - // Create proxy items - let group_items: Vec> = all_proxies - .iter() - .filter_map(|proxy_name| proxy_name.as_str()) - .filter_map(|proxy_str| { - let is_selected = proxy_str == now_proxy; - let item_id = format!("proxy_{}_{}", group_name, proxy_str); - - // Get delay for display - let delay_text = proxies - .get(proxy_str) - .and_then(|p| p.get("history")) - .and_then(|h| h.as_array()) - .and_then(|h| h.last()) - .and_then(|r| r.get("delay")) - .and_then(|d| d.as_i64()) - .map(|delay| match delay { - -1 => "-ms".to_string(), - delay if delay >= 10000 => "-ms".to_string(), - _ => format!("{}ms", delay), - }) - .unwrap_or_else(|| "-ms".to_string()); - - let display_text = format!("{} | {}", proxy_str, delay_text); - - CheckMenuItem::with_id( - app_handle, - item_id, - display_text, - true, - is_selected, - None::<&str>, - ) - .map_err(|e| log::warn!(target: "app", "创建代理菜单项失败: {}", e)) - .ok() - }) - .collect(); - - if group_items.is_empty() { - continue; - } - - // Determine if group is active - let is_group_active = match mode { - "global" => group_name == "GLOBAL" && !now_proxy.is_empty(), - "direct" => false, - _ => { - current_profile_selected - .iter() - .any(|s| s.name.as_deref() == Some(group_name)) - && !now_proxy.is_empty() - } - }; - - let group_display_name = if is_group_active { - format!("✓ {}", group_name) - } else { - group_name.to_string() - }; - - let group_items_refs: Vec<&dyn IsMenuItem> = group_items - .iter() - .map(|item| item as &dyn IsMenuItem) - .collect(); - - if let Ok(submenu) = Submenu::with_id_and_items( - app_handle, - format!("proxy_group_{}", group_name), - group_display_name, - true, - &group_items_refs, - ) { - group_name_submenus_hash.insert(group_name.to_string(), submenu); - } else { - log::warn!(target: "app", "创建代理组子菜单失败: {}", group_name); - } + if let Ok(submenu) = Submenu::with_id_and_items( + app_handle, + format!("proxy_group_{}", group_name), + group_display_name, + true, + &group_items_refs, + ) { + group_name_submenus_hash.insert(group_name.to_string(), submenu); + } else { + log::warn!(target: "app", "创建代理组子菜单失败: {}", group_name); } } @@ -1069,16 +1029,16 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { log::error!(target: "app", "切换代理失败: {} -> {}, 错误: {:?}", group_name, proxy_name, e); // Fallback to IPC update - if (IpcManager::global() - .update_proxy(group_name, proxy_name) + if (handle::Handle::mihomo() + .await + .select_node_for_group(group_name, proxy_name) .await) .is_ok() { log::info!(target: "app", "代理切换回退成功: {} -> {}", group_name, proxy_name); - if let Some(app_handle) = handle::Handle::global().app_handle() { - let _ = app_handle.emit("verge://force-refresh-proxies", ()); - } + let app_handle = handle::Handle::app_handle(); + let _ = app_handle.emit("verge://force-refresh-proxies", ()); } } } diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index 74e4a961..b0f36230 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -1,7 +1,6 @@ use crate::{ config::Config, core::{CoreManager, handle, tray}, - ipc::IpcManager, logging_error, process::AsyncHandler, utils::{logging::Type, resolve}, @@ -27,30 +26,18 @@ pub async fn restart_app() { // logging_error!(Type::Core, true, CoreManager::global().stop_core().await); resolve::resolve_reset_async().await; - handle::Handle::global() - .app_handle() - .map(|app_handle| { - app_handle.restart(); - }) - .unwrap_or_else(|| { - logging_error!( - Type::System, - false, - "{}", - "Failed to get app handle for restart" - ); - }); + let app_handle = handle::Handle::app_handle(); + app_handle.restart(); } fn after_change_clash_mode() { AsyncHandler::spawn(move || async { - match IpcManager::global().get_connections().await { + let mihomo = handle::Handle::mihomo().await; + match mihomo.get_connections().await { Ok(connections) => { - if let Some(connections_array) = connections["connections"].as_array() { + if let Some(connections_array) = connections.connections { for connection in connections_array { - if let Some(id) = connection["id"].as_str() { - let _ = IpcManager::global().delete_connection(id).await; - } + let _ = mihomo.close_connection(&connection.id).await; } } } @@ -70,7 +57,11 @@ pub async fn change_clash_mode(mode: String) { "mode": mode }); log::debug!(target: "app", "change clash mode to {mode}"); - match IpcManager::global().patch_configs(json_value).await { + match handle::Handle::mihomo() + .await + .patch_base_config(&json_value) + .await + { Ok(_) => { // 更新订阅 Config::clash().await.data_mut().patch_config(mapping); @@ -80,11 +71,7 @@ pub async fn change_clash_mode(mode: String) { if clash_data.save_config().await.is_ok() { handle::Handle::refresh_clash(); logging_error!(Type::Tray, true, tray::Tray::global().update_menu().await); - logging_error!( - Type::Tray, - true, - tray::Tray::global().update_icon(None).await - ); + logging_error!(Type::Tray, true, tray::Tray::global().update_icon().await); } let is_auto_close_connection = Config::verge() diff --git a/src-tauri/src/feat/config.rs b/src-tauri/src/feat/config.rs index 15f0325b..c62a51b1 100644 --- a/src-tauri/src/feat/config.rs +++ b/src-tauri/src/feat/config.rs @@ -23,11 +23,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { } else { if patch.get("mode").is_some() { logging_error!(Type::Tray, true, tray::Tray::global().update_menu().await); - logging_error!( - Type::Tray, - true, - tray::Tray::global().update_icon(None).await - ); + logging_error!(Type::Tray, true, tray::Tray::global().update_icon().await); } Config::runtime().await.draft_mut().patch_config(patch); CoreManager::global().update_config().await?; @@ -211,7 +207,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { tray::Tray::global().update_menu().await?; } if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 { - tray::Tray::global().update_icon(None).await?; + tray::Tray::global().update_icon().await?; } if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 { tray::Tray::global().update_tooltip().await?; diff --git a/src-tauri/src/feat/profile.rs b/src-tauri/src/feat/profile.rs index 2bf2249c..2eb92274 100644 --- a/src-tauri/src/feat/profile.rs +++ b/src-tauri/src/feat/profile.rs @@ -143,15 +143,15 @@ pub async fn update_profile( Ok(_) => { logging!(info, Type::Config, true, "[订阅更新] 更新成功"); handle::Handle::refresh_clash(); - if let Err(err) = cmd::proxy::force_refresh_proxies().await { - logging!( - error, - Type::Config, - true, - "[订阅更新] 代理组刷新失败: {}", - err - ); - } + // if let Err(err) = cmd::proxy::force_refresh_proxies().await { + // logging!( + // error, + // Type::Config, + // true, + // "[订阅更新] 代理组刷新失败: {}", + // err + // ); + // } } Err(err) => { logging!(error, Type::Config, true, "[订阅更新] 更新失败: {}", err); diff --git a/src-tauri/src/feat/proxy.rs b/src-tauri/src/feat/proxy.rs index 5934193b..7d520a89 100644 --- a/src-tauri/src/feat/proxy.rs +++ b/src-tauri/src/feat/proxy.rs @@ -1,9 +1,6 @@ use crate::{ config::{Config, IVerge}, core::handle, - ipc::IpcManager, - logging, - utils::logging::Type, }; use std::env; use tauri_plugin_clipboard_manager::ClipboardExt; @@ -26,7 +23,7 @@ pub async fn toggle_system_proxy() { // 如果当前系统代理即将关闭,且自动关闭连接设置为true,则关闭所有连接 if enable && auto_close_connection - && let Err(err) = IpcManager::global().close_all_connections().await + && let Err(err) = handle::Handle::mihomo().await.close_all_connections().await { log::error!(target: "app", "Failed to close all connections: {err}"); } @@ -78,14 +75,7 @@ pub async fn copy_clash_env() { .unwrap_or_else(|| "127.0.0.1".to_string()), }; - let Some(app_handle) = handle::Handle::global().app_handle() else { - logging!( - error, - Type::System, - "Failed to get app handle for proxy operation" - ); - return; - }; + let app_handle = handle::Handle::app_handle(); let port = { Config::verge() .await diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index 0188b27a..c61b1cd5 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -2,7 +2,6 @@ use crate::utils::window_manager::WindowManager; use crate::{ config::Config, core::{CoreManager, handle, sysopt}, - ipc::IpcManager, logging, module::lightweight, utils::logging::Type, @@ -25,18 +24,11 @@ pub async fn quit() { logging!(debug, Type::System, true, "启动退出流程"); // 获取应用句柄并设置退出标志 - let Some(app_handle) = handle::Handle::global().app_handle() else { - logging!( - error, - Type::System, - "Failed to get app handle for quit operation" - ); - return; - }; + let app_handle = handle::Handle::app_handle(); handle::Handle::global().set_is_exiting(); // 优先关闭窗口,提供立即反馈 - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let _ = window.hide(); log::info!(target: "app", "窗口已隐藏"); } @@ -70,7 +62,9 @@ async fn clean_async() -> bool { let disable_tun = serde_json::json!({"tun": {"enable": false}}); match timeout( Duration::from_secs(3), - IpcManager::global().patch_configs(disable_tun), + handle::Handle::mihomo() + .await + .patch_base_config(&disable_tun), ) .await { @@ -216,7 +210,7 @@ pub async fn hide() { add_light_weight_timer().await; } - if let Some(window) = handle::Handle::global().get_window() + if let Some(window) = handle::Handle::get_window() && window.is_visible().unwrap_or(false) { let _ = window.hide(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 44683d27..b3276f09 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,28 +7,30 @@ pub mod config; mod core; mod enhance; mod feat; -mod ipc; +// mod ipc; mod module; mod process; mod utils; #[cfg(target_os = "macos")] use crate::utils::window_manager::WindowManager; use crate::{ - core::handle, core::hotkey, process::AsyncHandler, utils::{resolve, server}, }; use config::Config; -use tauri::AppHandle; +use once_cell::sync::OnceCell; #[cfg(target_os = "macos")] use tauri::Manager; +use tauri::{AppHandle, Manager}; #[cfg(target_os = "macos")] use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_deep_link::DeepLinkExt; use tokio::time::{Duration, timeout}; use utils::logging::Type; +pub static APP_HANDLE: OnceCell = OnceCell::new(); + /// Application initialization helper functions mod app_init { use super::*; @@ -41,7 +43,7 @@ mod app_init { Ok(result) => { if result.is_err() { logging!(info, Type::Setup, true, "检测到已有应用实例运行"); - if let Some(app_handle) = handle::Handle::global().app_handle() { + if let Some(app_handle) = APP_HANDLE.get() { app_handle.exit(0); } else { std::process::exit(0); @@ -75,7 +77,13 @@ mod app_init { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_deep_link::init()) - .plugin(tauri_plugin_http::init()); + .plugin(tauri_plugin_http::init()) + .plugin( + tauri_plugin_mihomo::Builder::new() + .protocol(tauri_plugin_mihomo::models::Protocol::LocalSocket) + .socket_path(crate::config::IClashTemp::guard_external_controller_ipc()) + .build(), + ); // Devtools plugin only in debug mode with feature tauri-dev // to avoid duplicated registering of logger since the devtools plugin also registers a logger @@ -184,9 +192,9 @@ mod app_init { cmd::update_proxy_chain_config_in_runtime, cmd::invoke_uwp_tool, cmd::copy_clash_env, - cmd::get_proxies, - cmd::force_refresh_proxies, - cmd::get_providers_proxies, + // cmd::get_proxies, + // cmd::force_refresh_proxies, + // cmd::get_providers_proxies, cmd::sync_tray_proxy_selection, cmd::update_proxy_and_sync, cmd::save_dns_config, @@ -194,36 +202,36 @@ mod app_init { cmd::check_dns_config_exists, cmd::get_dns_config_content, cmd::validate_dns_config, - cmd::get_clash_version, - cmd::get_clash_config, - cmd::force_refresh_clash_config, - cmd::update_geo_data, - cmd::upgrade_clash_core, - cmd::get_clash_rules, - cmd::update_proxy_choice, - cmd::get_proxy_providers, - cmd::get_rule_providers, - cmd::proxy_provider_health_check, - cmd::update_proxy_provider, - cmd::update_rule_provider, - cmd::get_clash_connections, - cmd::delete_clash_connection, - cmd::close_all_clash_connections, - cmd::get_group_proxy_delays, - cmd::is_clash_debug_enabled, - cmd::clash_gc, + // cmd::get_clash_version, + // cmd::get_clash_config, + // cmd::force_refresh_clash_config, + // cmd::update_geo_data, + // cmd::upgrade_clash_core, + // cmd::get_clash_rules, + // cmd::update_proxy_choice, + // cmd::get_proxy_providers, + // cmd::get_rule_providers, + // cmd::proxy_provider_health_check, + // cmd::update_proxy_provider, + // cmd::update_rule_provider, + // cmd::get_clash_connections, + // cmd::delete_clash_connection, + // cmd::close_all_clash_connections, + // cmd::get_group_proxy_delays, + // cmd::is_clash_debug_enabled, + // cmd::clash_gc, // Logging and monitoring cmd::get_clash_logs, - cmd::start_logs_monitoring, - cmd::stop_logs_monitoring, - cmd::clear_logs, - cmd::get_traffic_data, - cmd::get_memory_data, - cmd::get_formatted_traffic_data, - cmd::get_formatted_memory_data, - cmd::get_system_monitor_overview, - cmd::start_traffic_service, - cmd::stop_traffic_service, + // cmd::start_logs_monitoring, + // cmd::stop_logs_monitoring, + // cmd::clear_logs, + // cmd::get_traffic_data, + // cmd::get_memory_data, + // cmd::get_formatted_traffic_data, + // cmd::get_formatted_memory_data, + // cmd::get_system_monitor_overview, + // cmd::start_traffic_service, + // cmd::stop_traffic_service, // Verge configuration cmd::get_verge_config, cmd::patch_verge_config, @@ -252,7 +260,7 @@ mod app_init { cmd::script_validate_notice, cmd::validate_script_file, // Clash API - cmd::clash_api_get_proxy_delay, + // cmd::clash_api_get_proxy_delay, // Backup and WebDAV cmd::create_webdav_backup, cmd::save_webdav_config, @@ -306,6 +314,10 @@ pub fn run() { .setup(|app| { logging!(info, Type::Setup, true, "开始应用初始化..."); + APP_HANDLE + .set(app.app_handle().clone()) + .expect("failed to set global app handle"); + // Setup autostart plugin if let Err(e) = app_init::setup_autostart(app) { logging!(error, Type::Setup, true, "Failed to setup autostart: {}", e); @@ -333,11 +345,9 @@ pub fn run() { ); } - let app_handle = app.handle().clone(); - logging!(info, Type::Setup, true, "执行主要设置操作..."); - resolve::resolve_setup_handle(app_handle); + resolve::resolve_setup_handle(); resolve::resolve_setup_async(); resolve::resolve_setup_sync(); @@ -353,13 +363,13 @@ pub fn run() { use super::*; /// Handle application ready/resumed events - pub fn handle_ready_resumed(app_handle: &AppHandle) { + pub fn handle_ready_resumed(_app_handle: &AppHandle) { logging!(info, Type::System, true, "应用就绪或恢复"); - handle::Handle::global().init(app_handle.clone()); + handle::Handle::global().init(); #[cfg(target_os = "macos")] { - if let Some(window) = app_handle.get_webview_window("main") { + if let Some(window) = _app_handle.get_webview_window("main") { logging!(info, Type::Window, true, "设置macOS窗口标题"); let _ = window.set_title("Clash Verge"); } @@ -410,7 +420,7 @@ pub fn run() { log::info!(target: "app", "closing window..."); if let tauri::WindowEvent::CloseRequested { api, .. } = api { api.prevent_close(); - if let Some(window) = core::handle::Handle::global().get_window() { + if let Some(window) = core::handle::Handle::get_window() { let _ = window.hide(); } else { logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在"); diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index ba42963b..6f7cf775 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -219,7 +219,7 @@ pub async fn add_light_weight_timer() { } fn setup_window_close_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = window.listen("tauri://close-requested", move |_event| { std::mem::drop(AsyncHandler::spawn(|| async { if let Err(e) = setup_light_weight_timer().await { @@ -239,7 +239,7 @@ fn setup_window_close_listener() { } fn cancel_window_close_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = WINDOW_CLOSE_HANDLER.swap(0, Ordering::AcqRel); if handler != 0 { window.unlisten(handler); @@ -249,7 +249,7 @@ fn cancel_window_close_listener() { } fn setup_webview_focus_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = window.listen("tauri://focus", move |_event| { log_err!(cancel_light_weight_timer()); logging!( @@ -264,7 +264,7 @@ fn setup_webview_focus_listener() { } fn cancel_webview_focus_listener() { - if let Some(window) = handle::Handle::global().get_window() { + if let Some(window) = handle::Handle::get_window() { let handler = WEBVIEW_FOCUS_HANDLER.swap(0, Ordering::AcqRel); if handler != 0 { window.unlisten(handler); diff --git a/src-tauri/src/module/sysinfo.rs b/src-tauri/src/module/sysinfo.rs index fc348f49..fb2b2579 100644 --- a/src-tauri/src/module/sysinfo.rs +++ b/src-tauri/src/module/sysinfo.rs @@ -38,17 +38,7 @@ impl PlatformSpecification { let system_kernel_version = System::kernel_version().unwrap_or("Null".into()); let system_arch = System::cpu_arch(); - let Some(handler) = handle::Handle::global().app_handle() else { - return Self { - system_name, - system_version, - system_kernel_version, - system_arch, - verge_version: "unknown".into(), - running_mode: "NotRunning".to_string(), - is_admin: false, - }; - }; + let handler = handle::Handle::app_handle(); let verge_version = handler.package_info().version.to_string(); // 使用默认值避免在同步上下文中执行异步操作 diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index 9b6c681e..714e644c 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -51,53 +51,7 @@ pub fn app_home_dir() -> Result { } // 避免在Handle未初始化时崩溃 - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "app_handle not initialized, using default path"); - // 使用可执行文件目录作为备用 - let exe_path = tauri::utils::platform::current_exe()?; - let exe_dir = exe_path - .parent() - .ok_or(anyhow::anyhow!("failed to get executable directory"))?; - - // 使用系统临时目录 + 应用ID - #[cfg(target_os = "windows")] - { - if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") { - let path = PathBuf::from(local_app_data).join(APP_ID); - return Ok(path); - } - } - - #[cfg(target_os = "macos")] - { - if let Some(home) = std::env::var_os("HOME") { - let path = PathBuf::from(home) - .join("Library") - .join("Application Support") - .join(APP_ID); - return Ok(path); - } - } - - #[cfg(target_os = "linux")] - { - if let Some(home) = std::env::var_os("HOME") { - let path = PathBuf::from(home) - .join(".local") - .join("share") - .join(APP_ID); - return Ok(path); - } - } - - // 如果无法获取系统目录,则回退到可执行文件目录 - let fallback_dir = PathBuf::from(exe_dir).join(".config").join(APP_ID); - log::warn!(target: "app", "Using fallback data directory: {fallback_dir:?}"); - return Ok(fallback_dir); - } - }; + let app_handle = handle::Handle::app_handle(); match app_handle.path().data_dir() { Ok(dir) => Ok(dir.join(APP_ID)), @@ -111,18 +65,7 @@ pub fn app_home_dir() -> Result { /// get the resources dir pub fn app_resources_dir() -> Result { // 避免在Handle未初始化时崩溃 - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - log::warn!(target: "app", "app_handle not initialized in app_resources_dir, using fallback"); - // 使用可执行文件目录作为备用 - let exe_dir = tauri::utils::platform::current_exe()? - .parent() - .ok_or(anyhow::anyhow!("failed to get executable directory"))? - .to_path_buf(); - return Ok(exe_dir.join("resources")); - } - }; + let app_handle = handle::Handle::app_handle(); match app_handle.path().resource_dir() { Ok(dir) => Ok(dir.join("resources")), diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 325fbbb7..09b356e0 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -494,15 +494,7 @@ pub fn init_scheme() -> Result<()> { } pub async fn startup_script() -> Result<()> { - let app_handle = match handle::Handle::global().app_handle() { - Some(handle) => handle, - None => { - return Err(anyhow::anyhow!( - "app_handle not available for startup script execution" - )); - } - }; - + let app_handle = handle::Handle::app_handle(); let script_path = { let verge = Config::verge().await; let verge = verge.latest_ref(); diff --git a/src-tauri/src/utils/resolve/dns.rs b/src-tauri/src/utils/resolve/dns.rs index e3618178..5d906b4a 100644 --- a/src-tauri/src/utils/resolve/dns.rs +++ b/src-tauri/src/utils/resolve/dns.rs @@ -2,7 +2,7 @@ pub async fn set_public_dns(dns_server: String) { use crate::{core::handle, utils::dirs}; use tauri_plugin_shell::ShellExt; - let app_handle = match handle::Handle::global().app_handle() { + let app_handle = match handle::Handle::app_handle() { Some(handle) => handle, None => { log::error!(target: "app", "app_handle not available for DNS configuration"); @@ -50,7 +50,7 @@ pub async fn set_public_dns(dns_server: String) { pub async fn restore_public_dns() { use crate::{core::handle, utils::dirs}; use tauri_plugin_shell::ShellExt; - let app_handle = match handle::Handle::global().app_handle() { + let app_handle = match handle::Handle::app_handle() { Some(handle) => handle, None => { log::error!(target: "app", "app_handle not available for DNS restoration"); diff --git a/src-tauri/src/utils/resolve/mod.rs b/src-tauri/src/utils/resolve/mod.rs index 1bee81fd..7a3f7344 100644 --- a/src-tauri/src/utils/resolve/mod.rs +++ b/src-tauri/src/utils/resolve/mod.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use tauri::AppHandle; use crate::{ config::Config, @@ -18,8 +17,8 @@ pub mod ui; pub mod window; pub mod window_script; -pub fn resolve_setup_handle(app_handle: AppHandle) { - init_handle(app_handle); +pub fn resolve_setup_handle() { + init_handle(); } pub fn resolve_setup_sync() { @@ -118,9 +117,9 @@ pub async fn resolve_reset_async() { } } -pub fn init_handle(app_handle: AppHandle) { +pub fn init_handle() { logging!(info, Type::Setup, true, "Initializing app handle..."); - handle::Handle::global().init(app_handle); + handle::Handle::global().init(); } pub(super) fn init_scheme() { diff --git a/src-tauri/src/utils/resolve/window.rs b/src-tauri/src/utils/resolve/window.rs index e99e3307..31036f43 100644 --- a/src-tauri/src/utils/resolve/window.rs +++ b/src-tauri/src/utils/resolve/window.rs @@ -2,7 +2,7 @@ use tauri::WebviewWindow; use crate::{ core::handle, - logging, logging_error, + logging_error, utils::{ logging::Type, resolve::window_script::{INITIAL_LOADING_OVERLAY, WINDOW_INITIAL_SCRIPT}, @@ -18,18 +18,10 @@ const MINIMAL_HEIGHT: f64 = 520.0; /// 构建新的 WebView 窗口 pub fn build_new_window() -> Result { - let app_handle = handle::Handle::global().app_handle().ok_or_else(|| { - logging!( - error, - Type::Window, - true, - "无法获取app_handle,窗口创建失败" - ); - "无法获取app_handle".to_string() - })?; + let app_handle = handle::Handle::app_handle(); match tauri::WebviewWindowBuilder::new( - &app_handle, + app_handle, "main", /* the unique window label */ tauri::WebviewUrl::App("index.html".into()), ) diff --git a/src-tauri/src/utils/window_manager.rs b/src-tauri/src/utils/window_manager.rs index d982a8e2..7ce5ce36 100644 --- a/src-tauri/src/utils/window_manager.rs +++ b/src-tauri/src/utils/window_manager.rs @@ -67,7 +67,7 @@ fn should_handle_window_operation() -> bool { let now = Instant::now(); let elapsed = now.duration_since(*last_operation); - log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)", + log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)", elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS); if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) { @@ -76,7 +76,7 @@ fn should_handle_window_operation() -> bool { log::info!(target: "app", "[防抖] 窗口操作被允许执行"); true } else { - log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms", + log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms", elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS); false } @@ -117,9 +117,8 @@ impl WindowManager { /// 获取主窗口实例 pub fn get_main_window() -> Option> { - handle::Handle::global() - .app_handle() - .and_then(|app| app.get_webview_window("main")) + let app_handle = handle::Handle::app_handle(); + app_handle.get_webview_window("main") } /// 智能显示主窗口 diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 747e9909..8e2c72ce 100755 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -49,8 +49,11 @@ "security": { "capabilities": ["desktop-capability", "migrated"], "assetProtocol": { - "scope": ["$APPDATA/**", "$RESOURCE/../**", "**"], - "enable": true + "enable": true, + "scope": { + "allow": ["**"], + "requireLiteralLeadingDot": false + } }, "csp": null } diff --git a/src/components/connection/connection-detail.tsx b/src/components/connection/connection-detail.tsx index 9eb0f563..b6d2a8c8 100644 --- a/src/components/connection/connection-detail.tsx +++ b/src/components/connection/connection-detail.tsx @@ -4,8 +4,8 @@ import dayjs from "dayjs"; import { t } from "i18next"; import { forwardRef, useImperativeHandle, useState } from "react"; -import { deleteConnection } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; +import { closeConnections } from "tauri-plugin-mihomo-api"; export interface ConnectionDetailRef { open: (detail: IConnectionsItem) => void; @@ -99,7 +99,7 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => { { label: t("Type"), value: `${metadata.type}(${metadata.network})` }, ]; - const onDelete = useLockFn(async () => deleteConnection(data.id)); + const onDelete = useLockFn(async () => closeConnections(data.id)); return ( diff --git a/src/components/connection/connection-item.tsx b/src/components/connection/connection-item.tsx index 77d2c7e9..370f4fa4 100644 --- a/src/components/connection/connection-item.tsx +++ b/src/components/connection/connection-item.tsx @@ -10,8 +10,8 @@ import { import { useLockFn } from "ahooks"; import dayjs from "dayjs"; -import { deleteConnection } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; +import { closeConnections } from "tauri-plugin-mihomo-api"; const Tag = styled("span")(({ theme }) => ({ fontSize: "10px", @@ -34,7 +34,7 @@ export const ConnectionItem = (props: Props) => { const { id, metadata, chains, start, curUpload, curDownload } = value; - const onDelete = useLockFn(async () => deleteConnection(id)); + const onDelete = useLockFn(async () => closeConnections(id)); const showTraffic = curUpload! >= 100 || curDownload! >= 100; return ( diff --git a/src/components/home/clash-mode-card.tsx b/src/components/home/clash-mode-card.tsx index ac3a8136..c10b3826 100644 --- a/src/components/home/clash-mode-card.tsx +++ b/src/components/home/clash-mode-card.tsx @@ -10,8 +10,8 @@ import { useTranslation } from "react-i18next"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-provider"; -import { closeAllConnections } from "@/services/cmds"; import { patchClashMode } from "@/services/cmds"; +import { closeAllConnections } from "tauri-plugin-mihomo-api"; export const ClashModeCard = () => { const { t } = useTranslation(); diff --git a/src/components/home/enhanced-traffic-stats.tsx b/src/components/home/enhanced-traffic-stats.tsx index e049e06a..8d19033d 100644 --- a/src/components/home/enhanced-traffic-stats.tsx +++ b/src/components/home/enhanced-traffic-stats.tsx @@ -13,25 +13,23 @@ import { useTheme, PaletteColor, Grid, - Box, } from "@mui/material"; -import { useRef, useCallback, memo, useMemo } from "react"; +import { useRef, memo, useMemo } from "react"; import { ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import useSWR from "swr"; import { TrafficErrorBoundary } from "@/components/common/traffic-error-boundary"; -import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor"; import { useVerge } from "@/hooks/use-verge"; import { useVisibility } from "@/hooks/use-visibility"; -import { useAppData } from "@/providers/app-data-provider"; -import { isDebugEnabled, gc } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; import { EnhancedCanvasTrafficGraph, type EnhancedCanvasTrafficGraphRef, } from "./enhanced-canvas-traffic-graph"; +import { useConnectionData } from "@/hooks/use-connection-data"; +import { useTrafficData } from "@/hooks/use-traffic-data"; +import { useMemoryData } from "@/hooks/use-memory-data"; interface StatCardProps { icon: ReactNode; @@ -149,51 +147,33 @@ export const EnhancedTrafficStats = () => { const trafficRef = useRef(null); const pageVisible = useVisibility(); - // 使用AppDataProvider - const { connections } = useAppData(); + const { + response: { data: traffic }, + } = useTrafficData(); - // 使用增强版的统一流量数据Hook - const { traffic, memory, isLoading, isDataFresh, hasValidData } = - useTrafficDataEnhanced(); + const { + response: { data: memory }, + } = useMemoryData(); + + const { + response: { data: connections }, + } = useConnectionData(); // 是否显示流量图表 const trafficGraph = verge?.traffic_graph ?? true; - // 检查是否支持调试 - // TODO: merge this hook with layout-traffic.tsx - const { data: isDebug } = useSWR( - `clash-verge-rev-internal://isDebugEnabled`, - () => isDebugEnabled(), - { - // default value before is fetched - fallbackData: false, - }, - ); - // Canvas组件现在直接从全局Hook获取数据,无需手动添加数据点 - // 执行垃圾回收 - const handleGarbageCollection = useCallback(async () => { - if (isDebug) { - try { - await gc(); - console.log("[Debug] 垃圾回收已执行"); - } catch (err) { - console.error("[Debug] 垃圾回收失败:", err); - } - } - }, [isDebug]); - // 使用useMemo计算解析后的流量数据 const parsedData = useMemo(() => { - const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0); - const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0); - const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0); + const [up, upUnit] = parseTraffic(traffic?.up || 0); + const [down, downUnit] = parseTraffic(traffic?.down || 0); + const [inuse, inuseUnit] = parseTraffic(memory?.inuse || 0); const [uploadTotal, uploadTotalUnit] = parseTraffic( - connections.uploadTotal, + connections?.uploadTotal, ); const [downloadTotal, downloadTotalUnit] = parseTraffic( - connections.downloadTotal, + connections?.downloadTotal, ); return { @@ -207,7 +187,7 @@ export const EnhancedTrafficStats = () => { uploadTotalUnit, downloadTotal, downloadTotalUnit, - connectionsCount: connections.count, + connectionsCount: connections?.connections.length, }; }, [traffic, memory, connections]); @@ -229,33 +209,10 @@ export const EnhancedTrafficStats = () => { >
- {isDebug && ( -
- DEBUG: {trafficRef.current ? "图表已初始化" : "图表未初始化"} -
- 状态: {isDataFresh ? "active" : "inactive"} -
- 数据新鲜度: {traffic?.is_fresh ? "Fresh" : "Stale"} -
- {new Date().toISOString().slice(11, 19)} -
- )}
); - }, [trafficGraph, pageVisible, theme.palette.divider, isDebug]); + }, [trafficGraph, pageVisible, theme.palette.divider]); // 使用useMemo计算统计卡片配置 const statCards = useMemo( @@ -301,10 +258,10 @@ export const EnhancedTrafficStats = () => { value: parsedData.inuse, unit: parsedData.inuseUnit, color: "error" as const, - onClick: isDebug ? handleGarbageCollection : undefined, + onClick: undefined, }, ], - [t, parsedData, isDebug, handleGarbageCollection], + [t, parsedData], ); return ( @@ -321,28 +278,11 @@ export const EnhancedTrafficStats = () => { )} {/* 统计卡片区域 */} - {statCards.map((card, index) => ( - + {statCards.map((card, _index) => ( + ))} - - {/* 数据状态指示器(调试用)*/} - {isDebug && ( - - - 数据状态: {isDataFresh ? "新鲜" : "过期"} | 有效数据:{" "} - {hasValidData ? "是" : "否"} | 加载中: {isLoading ? "是" : "否"} - - - )} ); diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 71f662f2..fd052692 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -6,34 +6,19 @@ import { import { Box, Typography } from "@mui/material"; import { useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; -import useSWR from "swr"; import { LightweightTrafficErrorBoundary } from "@/components/common/traffic-error-boundary"; -import { useClashInfo } from "@/hooks/use-clash"; -import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor"; import { useVerge } from "@/hooks/use-verge"; import { useVisibility } from "@/hooks/use-visibility"; -import { isDebugEnabled, gc, startTrafficService } from "@/services/cmds"; import parseTraffic from "@/utils/parse-traffic"; import { TrafficGraph, type TrafficRef } from "./traffic-graph"; +import { useTrafficData } from "@/hooks/use-traffic-data"; +import { useMemoryData } from "@/hooks/use-memory-data"; // setup the traffic export const LayoutTraffic = () => { - const { data: isDebug } = useSWR( - "clash-verge-rev-internal://isDebugEnabled", - () => isDebugEnabled(), - { - // default value before is fetched - fallbackData: false, - }, - ); - - if (isDebug) { - console.debug("[Traffic][LayoutTraffic] 组件正在渲染"); - } const { t } = useTranslation(); - const { clashInfo } = useClashInfo(); const { verge } = useVerge(); // whether hide traffic graph @@ -42,31 +27,19 @@ export const LayoutTraffic = () => { const trafficRef = useRef(null); const pageVisible = useVisibility(); - // 使用增强版的统一流量数据Hook - const { traffic, memory } = useTrafficDataEnhanced(); - - // 启动流量服务 - useEffect(() => { - console.log( - "[Traffic][LayoutTraffic] useEffect 触发,clashInfo:", - clashInfo, - "pageVisible:", - pageVisible, - ); - - // 简化条件,只要组件挂载就尝试启动服务 - console.log("[Traffic][LayoutTraffic] 开始启动流量服务"); - startTrafficService().catch((error) => { - console.error("[Traffic][LayoutTraffic] 启动流量服务失败:", error); - }); - }, []); // 移除依赖,只在组件挂载时启动一次 + const { + response: { data: traffic }, + } = useTrafficData(); + const { + response: { data: memory }, + } = useMemoryData(); // 监听数据变化,为图表添加数据点 useEffect(() => { - if (traffic?.raw && trafficRef.current) { + if (trafficRef.current) { trafficRef.current.appendData({ - up: traffic.raw.up_rate || 0, - down: traffic.raw.down_rate || 0, + up: traffic?.up || 0, + down: traffic?.down || 0, }); } }, [traffic]); @@ -75,9 +48,9 @@ export const LayoutTraffic = () => { const displayMemory = verge?.enable_memory_usage ?? true; // 使用parseTraffic统一处理转换,保持与首页一致的显示格式 - const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0); - const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0); - const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0); + const [up, upUnit] = parseTraffic(traffic?.up || 0); + const [down, downUnit] = parseTraffic(traffic?.down || 0); + const [inuse, inuseUnit] = parseTraffic(memory?.inuse || 0); const boxStyle: any = { display: "flex", @@ -114,18 +87,16 @@ export const LayoutTraffic = () => { 0 ? "secondary" : "disabled" - } + color={(traffic?.up || 0) > 0 ? "secondary" : "disabled"} /> {up} @@ -134,18 +105,16 @@ export const LayoutTraffic = () => { 0 ? "primary" : "disabled" - } + color={(traffic?.down || 0) > 0 ? "primary" : "disabled"} /> {down} @@ -155,15 +124,15 @@ export const LayoutTraffic = () => { {displayMemory && ( { - isDebug && (await gc()); + // isDebug && (await gc()); }} > diff --git a/src/components/proxy/provider-button.tsx b/src/components/proxy/provider-button.tsx index 4b292109..89c7c500 100644 --- a/src/components/proxy/provider-button.tsx +++ b/src/components/proxy/provider-button.tsx @@ -22,9 +22,9 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useAppData } from "@/providers/app-data-provider"; -import { proxyProviderUpdate } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import parseTraffic from "@/utils/parse-traffic"; +import { updateProxyProvider } from "tauri-plugin-mihomo-api"; // 定义代理提供者类型 interface ProxyProviderItem { @@ -74,7 +74,7 @@ export const ProviderButton = () => { // 设置更新状态 setUpdating((prev) => ({ ...prev, [name]: true })); - await proxyProviderUpdate(name); + await updateProxyProvider(name); // 刷新数据 await refreshProxy(); @@ -115,7 +115,7 @@ export const ProviderButton = () => { // 改为串行逐个更新所有provider for (const name of allProviders) { try { - await proxyProviderUpdate(name); + await updateProxyProvider(name); // 每个更新完成后更新状态 setUpdating((prev) => ({ ...prev, [name]: false })); } catch (err) { @@ -177,161 +177,164 @@ export const ProviderButton = () => { - {Object.entries(proxyProviders || {}).map(([key, item]) => { - const provider = item as ProxyProviderItem; - const time = dayjs(provider.updatedAt); - const isUpdating = updating[key]; + {Object.entries(proxyProviders || {}) + .sort() + .map(([key, item]) => { + const provider = item as ProxyProviderItem; + const time = dayjs(provider.updatedAt); + const isUpdating = updating[key]; - // 订阅信息 - const sub = provider.subscriptionInfo; - const hasSubInfo = !!sub; - const upload = sub?.Upload || 0; - const download = sub?.Download || 0; - const total = sub?.Total || 0; - const expire = sub?.Expire || 0; + // 订阅信息 + const sub = provider.subscriptionInfo; + const hasSubInfo = !!sub; + const upload = sub?.Upload || 0; + const download = sub?.Download || 0; + const total = sub?.Total || 0; + const expire = sub?.Expire || 0; - // 流量使用进度 - const progress = - total > 0 - ? Math.min( - Math.round(((download + upload) * 100) / total) + 1, - 100, - ) - : 0; + // 流量使用进度 + const progress = + total > 0 + ? Math.min( + Math.round(((download + upload) * 100) / total) + 1, + 100, + ) + : 0; - return ( - { - const bgcolor = mode === "light" ? "#ffffff" : "#24252f"; - const hoverColor = - mode === "light" - ? alpha(primary.main, 0.1) - : alpha(primary.main, 0.2); + return ( + { + const bgcolor = + mode === "light" ? "#ffffff" : "#24252f"; + const hoverColor = + mode === "light" + ? alpha(primary.main, 0.1) + : alpha(primary.main, 0.2); - return { - backgroundColor: bgcolor, - "&:hover": { - backgroundColor: hoverColor, - }, - }; - }, - ]} - > - - - {key} - - {provider.proxies.length} - - - {provider.vehicleType} - - - - - {t("Update At")}: - {time.fromNow()} - - - } - secondary={ - <> - {/* 订阅信息 */} - {hasSubInfo && ( - <> - - - {parseTraffic(upload + download)} /{" "} - {parseTraffic(total)} - - - {parseExpire(expire)} - - - - {/* 进度条 */} - 0 ? 1 : 0, - }} - /> - - )} - - } - /> - - - { - updateProvider(key); - }} - disabled={isUpdating} + + + {key} + + {provider.proxies.length} + + + {provider.vehicleType} + + + + + {t("Update At")}: + {time.fromNow()} + + + } + secondary={ + <> + {/* 订阅信息 */} + {hasSubInfo && ( + <> + + + {parseTraffic(upload + download)} /{" "} + {parseTraffic(total)} + + + {parseExpire(expire)} + + + + {/* 进度条 */} + 0 ? 1 : 0, + }} + /> + + )} + + } + /> + + - - - - - ); - })} + { + updateProvider(key); + }} + disabled={isUpdating} + sx={{ + animation: isUpdating + ? "spin 1s linear infinite" + : "none", + "@keyframes spin": { + "0%": { transform: "rotate(0deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + }} + title={t("Update Provider") as string} + > + + + + + ); + })} diff --git a/src/components/proxy/proxy-chain.tsx b/src/components/proxy/proxy-chain.tsx index 57e87243..b2a060b2 100644 --- a/src/components/proxy/proxy-chain.tsx +++ b/src/components/proxy/proxy-chain.tsx @@ -37,11 +37,11 @@ import useSWR from "swr"; import { useAppData } from "@/providers/app-data-provider"; import { - closeAllConnections, - getProxies, + calcuProxies, updateProxyAndSync, updateProxyChainConfigInRuntime, } from "@/services/cmds"; +import { closeAllConnections } from "tauri-plugin-mihomo-api"; interface ProxyChainItem { id: string; @@ -204,7 +204,7 @@ export const ProxyChain = ({ // 获取当前代理信息以检查连接状态 const { data: currentProxies, mutate: mutateProxies } = useSWR( "getProxies", - getProxies, + calcuProxies, { revalidateOnFocus: true, revalidateIfStale: true, diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index 889e66b0..1afe7da0 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -3,29 +3,21 @@ import { Snackbar, Alert, Chip, - Stack, Typography, IconButton, - Collapse, Menu, MenuItem, - Divider, } from "@mui/material"; -import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material"; +import { ExpandMoreRounded } from "@mui/icons-material"; import { useLockFn } from "ahooks"; import { useRef, useState, useEffect, useCallback } from "react"; -import useSWR from "swr"; import { useTranslation } from "react-i18next"; import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"; import { useProxySelection } from "@/hooks/use-proxy-selection"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-provider"; -import { - providerHealthCheck, - getGroupProxyDelays, - updateProxyChainConfigInRuntime, -} from "@/services/cmds"; +import { updateProxyChainConfigInRuntime } from "@/services/cmds"; import delayManager from "@/services/delay"; import { BaseEmpty } from "../base"; @@ -34,6 +26,7 @@ import { ScrollTopButton } from "../layout/scroll-top-button"; import { ProxyChain } from "./proxy-chain"; import { ProxyRender } from "./proxy-render"; import { useRenderList } from "./use-render-list"; +import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api"; interface Props { mode: string; @@ -154,15 +147,14 @@ export const ProxyGroups = (props: Props) => { // 添加和清理滚动事件监听器 useEffect(() => { - const currentScroller = scrollerRef.current; - if (currentScroller) { - currentScroller.addEventListener("scroll", handleScroll, { - passive: true, - }); - return () => { - currentScroller.removeEventListener("scroll", handleScroll); - }; - } + if (!scrollerRef.current) return; + scrollerRef.current.addEventListener("scroll", handleScroll, { + passive: true, + }); + + return () => { + scrollerRef.current?.removeEventListener("scroll", handleScroll); + }; }, [handleScroll]); // 滚动到顶部 @@ -274,7 +266,7 @@ export const ProxyGroups = (props: Props) => { if (providers.size) { console.log(`[ProxyGroups] 发现提供者,数量: ${providers.size}`); Promise.allSettled( - [...providers].map((p) => providerHealthCheck(p)), + [...providers].map((p) => healthcheckProxyProvider(p)), ).then(() => { console.log(`[ProxyGroups] 提供者健康检查完成`); onProxies(); @@ -290,7 +282,7 @@ export const ProxyGroups = (props: Props) => { try { await Promise.race([ delayManager.checkListDelay(names, groupName, timeout), - getGroupProxyDelays(groupName, url, timeout).then((result) => { + delayGroup(groupName, url, timeout).then((result) => { console.log( `[ProxyGroups] getGroupProxyDelays返回结果数量:`, Object.keys(result || {}).length, diff --git a/src/components/rule/provider-button.tsx b/src/components/rule/provider-button.tsx index 622b143a..7cce5939 100644 --- a/src/components/rule/provider-button.tsx +++ b/src/components/rule/provider-button.tsx @@ -20,9 +20,9 @@ import dayjs from "dayjs"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { useAppData } from "@/providers/app-data-provider"; -import { ruleProviderUpdate } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; +import { updateRuleProvider } from "tauri-plugin-mihomo-api"; +import { useAppData } from "@/providers/app-data-provider"; // 定义规则提供者类型 interface RuleProviderItem { @@ -60,7 +60,7 @@ export const ProviderButton = () => { // 设置更新状态 setUpdating((prev) => ({ ...prev, [name]: true })); - await ruleProviderUpdate(name); + await updateRuleProvider(name); // 刷新数据 await refreshRules(); @@ -101,7 +101,7 @@ export const ProviderButton = () => { // 改为串行逐个更新所有provider for (const name of allProviders) { try { - await ruleProviderUpdate(name); + await updateRuleProvider(name); // 每个更新完成后更新状态 setUpdating((prev) => ({ ...prev, [name]: false })); } catch (err) { @@ -160,112 +160,117 @@ export const ProviderButton = () => { - {Object.entries(ruleProviders || {}).map(([key, item]) => { - const provider = item as RuleProviderItem; - const time = dayjs(provider.updatedAt); - const isUpdating = updating[key]; + {Object.entries(ruleProviders || {}) + .sort() + .map(([key, item]) => { + const provider = item as RuleProviderItem; + const time = dayjs(provider.updatedAt); + const isUpdating = updating[key]; - return ( - { - const bgcolor = mode === "light" ? "#ffffff" : "#24252f"; - const hoverColor = - mode === "light" - ? alpha(primary.main, 0.1) - : alpha(primary.main, 0.2); + return ( + { + const bgcolor = + mode === "light" ? "#ffffff" : "#24252f"; + const hoverColor = + mode === "light" + ? alpha(primary.main, 0.1) + : alpha(primary.main, 0.2); - return { - backgroundColor: bgcolor, - "&:hover": { - backgroundColor: hoverColor, - borderColor: alpha(primary.main, 0.3), - }, - }; - }, - ]} - > - - - {key} - - {provider.ruleCount} - - - - - {t("Update At")}: - {time.fromNow()} - - - } - secondary={ - - - {provider.vehicleType} - - {provider.behavior} - - } - /> - - - updateProvider(key)} - disabled={isUpdating} + + + {key} + + {provider.ruleCount} + + + + + {t("Update At")}: + {time.fromNow()} + + + } + secondary={ + + + {provider.vehicleType} + + + {provider.behavior} + + + } + /> + + - - - - - ); - })} + updateProvider(key)} + disabled={isUpdating} + sx={{ + animation: isUpdating + ? "spin 1s linear infinite" + : "none", + "@keyframes spin": { + "0%": { transform: "rotate(0deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + }} + title={t("Update Provider") as string} + > + + +
+ + ); + })} diff --git a/src/components/setting/mods/clash-core-viewer.tsx b/src/components/setting/mods/clash-core-viewer.tsx index 869b5784..1496c3b9 100644 --- a/src/components/setting/mods/clash-core-viewer.tsx +++ b/src/components/setting/mods/clash-core-viewer.tsx @@ -19,12 +19,8 @@ import { mutate } from "swr"; import { BaseDialog, DialogRef } from "@/components/base"; import { useVerge } from "@/hooks/use-verge"; import { changeClashCore, restartCore } from "@/services/cmds"; -import { - closeAllConnections, - upgradeCore, - forceRefreshClashConfig, -} from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; +import { closeAllConnections, upgradeCore } from "tauri-plugin-mihomo-api"; const VALID_CORE = [ { name: "Mihomo", core: "verge-mihomo", chip: "Release Version" }, @@ -64,8 +60,6 @@ export const ClashCoreViewer = forwardRef((props, ref) => { mutateVerge(); setTimeout(async () => { - // 核心切换后强制刷新配置缓存 - await forceRefreshClashConfig(); mutate("getClashConfig"); mutate("getVersion"); setChangingCore(null); diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx index 329d857e..6b05bb08 100644 --- a/src/components/setting/mods/sysproxy-viewer.tsx +++ b/src/components/setting/mods/sysproxy-viewer.tsx @@ -11,13 +11,7 @@ import { Typography, } from "@mui/material"; import { useLockFn } from "ahooks"; -import { - forwardRef, - useEffect, - useImperativeHandle, - useMemo, - useState, -} from "react"; +import { forwardRef, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import useSWR, { mutate } from "swr"; @@ -27,7 +21,6 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { EditorViewer } from "@/components/profile/editor-viewer"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-provider"; -import { getClashConfig } from "@/services/cmds"; import { getAutotemProxy, getNetworkInterfacesInfo, @@ -37,6 +30,7 @@ import { } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import getSystem from "@/utils/get-system"; +import { getBaseConfig } from "tauri-plugin-mihomo-api"; const DEFAULT_PAC = `function FindProxyForURL(url, host) { return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;"; @@ -123,26 +117,21 @@ export const SysproxyViewer = forwardRef((props, ref) => { return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,"; }; - const { data: clashConfig } = useSWR("getClashConfig", getClashConfig, { + const { data: clashConfig } = useSWR("getClashConfig", getBaseConfig, { revalidateOnFocus: false, revalidateIfStale: true, dedupingInterval: 1000, errorRetryInterval: 5000, }); - const [prevMixedPort, setPrevMixedPort] = useState( - clashConfig?.["mixed-port"], - ); + const [prevMixedPort, setPrevMixedPort] = useState(clashConfig?.mixedPort); - useEffect(() => { - if ( - clashConfig?.["mixed-port"] && - clashConfig?.["mixed-port"] !== prevMixedPort - ) { - setPrevMixedPort(clashConfig?.["mixed-port"]); - resetSystemProxy(); - } - }, [clashConfig?.["mixed-port"]]); + // useEffect(() => { + // if (clashConfig?.mixedPort && clashConfig.mixedPort !== prevMixedPort) { + // setPrevMixedPort(clashConfig.mixedPort); + // resetSystemProxy(); + // } + // }, [clashConfig]); const resetSystemProxy = async () => { try { @@ -180,7 +169,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (isPacMode) { const host = value.proxy_host || "127.0.0.1"; - const port = verge?.verge_mixed_port || clashConfig["mixed-port"] || 7897; + const port = verge?.verge_mixed_port || clashConfig.mixedPort || 7897; return `${host}:${port}`; } else { return systemProxyAddress; @@ -332,7 +321,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (pacContent) { pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host); // 将 mixed-port 转换为字符串 - const mixedPortStr = (clashConfig?.["mixed-port"] || "").toString(); + const mixedPortStr = (clashConfig?.mixedPort || "").toString(); pacContent = pacContent.replace(/%mixed-port%/g, mixedPortStr); } diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index 481683fa..4ca0756c 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -10,7 +10,6 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { useClash } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; import { invoke_uwp_tool } from "@/services/cmds"; -import { updateGeoData } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import getSystem from "@/utils/get-system"; @@ -23,6 +22,8 @@ import { GuardState } from "./mods/guard-state"; import { NetworkInterfaceViewer } from "./mods/network-interface-viewer"; import { SettingItem, SettingList } from "./mods/setting-comp"; import { WebUIViewer } from "./mods/web-ui-viewer"; +import { updateGeo } from "tauri-plugin-mihomo-api"; +import { useClashLog } from "@/services/states"; const isWIN = getSystem() === "windows"; @@ -35,6 +36,7 @@ const SettingClash = ({ onError }: Props) => { const { clash, version, mutateClash, patchClash } = useClash(); const { verge, patchVerge } = useVerge(); + const [clashLog, setClashLog] = useClashLog(); const { ipv6, @@ -64,7 +66,7 @@ const SettingClash = ({ onError }: Props) => { }; const onUpdateGeo = async () => { try { - await updateGeoData(); + await updateGeo(); showNotice("success", t("GeoData Updated")); } catch (err: any) { showNotice("error", err?.response.data.message || err.toString()); @@ -186,7 +188,10 @@ const SettingClash = ({ onError }: Props) => { onCatch={onError} onFormat={(e: any) => e.target.value} onChange={(e) => onChangeData({ "log-level": e })} - onGuard={(e) => patchClash({ "log-level": e })} + onGuard={(e) => { + setClashLog((pre: any) => ({ ...pre, logLevel: e })); + return patchClash({ "log-level": e }); + }} >