From bea0dde07450a275a07205d4467e7f11405d2046 Mon Sep 17 00:00:00 2001 From: "Junkai W." <129588175+Be-Forever223@users.noreply.github.com> Date: Sun, 31 Aug 2025 14:20:57 +0800 Subject: [PATCH] =?UTF-8?q?Win=20=E4=B8=8B=E6=B7=BB=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E8=8A=82=E7=82=B9=E7=9A=84=E7=B3=BB=E7=BB=9F=E6=89=98?= =?UTF-8?q?=E7=9B=98=20(#4562)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add proxy memu in tray * 添加win下系统托盘 节点 代理->代理组->nodes 同时添加了对应gui同步 * 添加win 系统托盘显示代理节点 且gui和托盘刷新机制 * rust format * 添加 win下系统托盘节点延迟 * Squashed commit of the following: commit 44caaa62c54be198718ad93638c97f2b56560149 Merge: 1916e539 3939741a Author: Junkai W. <129588175+Be-Forever223@users.noreply.github.com> Date: Sat Aug 30 02:37:07 2025 +0800 Merge branch 'dev' into dev commit 3939741a06b40d449c59a22351edac154e02fdbf Author: Tunglies Date: Sat Aug 30 02:24:47 2025 +0800 refactor: migrate from serde_yaml to serde_yaml_ng for improved YAML handling (#4568) * refactor: migrate from serde_yaml to serde_yaml_ng for improved YAML handling * refactor: format code for better readability in DNS configuration commit f86a1816e06e92f637e7ea317fd0819f952dc0e7 Author: Tunglies Date: Sat Aug 30 02:15:34 2025 +0800 chore(deps): update sysinfo to 0.37.0 and zip to 4.5.0 in Cargo.toml (#4564) * chore(deps): update sysinfo to 0.37.0 and zip to 4.5.0 in Cargo.toml * chore(deps): remove libnghttp2-sys dependency and update isahc features in Cargo.toml * chore(deps): remove sysinfo and zip from ignoreDeps in renovate.json commit 9cbd8b45293d3de62abdb72359295f87fa2f0b7f Author: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Sat Aug 30 01:30:48 2025 +0800 feat: add x86 OpenSSL installation step for macOS in workflows commit 5dea73fc2a3bdd5646665a508830ac30c524604a Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Aug 30 01:21:53 2025 +0800 chore(deps): update npm dependencies (#4542) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 01af1bea23dded6a9056c4987fe4f38680c4781e Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Aug 30 01:21:46 2025 +0800 chore(deps): update rust crate reqwest_dav to 0.2.2 (#4554) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> commit 1227e86134c998c6cd71dfbc8539a1a1b8b80a0f Author: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Sat Aug 30 01:12:03 2025 +0800 Remove unnecessary "rustls-tls" feature from reqwest dependency in Cargo.toml commit c6a6ea48dd23297deb8439f9e414059755330c65 Author: Tunglies Date: Fri Aug 29 23:51:09 2025 +0800 refactor: enhance async initialization and streamline setup process (#4560) * feat: Implement DNS management for macOS - Added `set_public_dns` and `restore_public_dns` functions in `dns.rs` to manage system DNS settings. - Introduced `resolve` module to encapsulate DNS and scheme resolution functionalities. - Implemented `resolve_scheme` function in `scheme.rs` to handle deep links and profile imports. - Created UI readiness management in `ui.rs` to track and update UI loading states. - Developed window management logic in `window.rs` to handle window creation and visibility. - Added initial loading overlay script in `window_script.rs` for better user experience during startup. - Updated server handling in `server.rs` to integrate new resolve functionalities. - Refactored window creation calls in `window_manager.rs` to use the new window management logic. * refactor: streamline asynchronous handling in config and resolve setup * Revert "refactor: streamline asynchronous handling in config and resolve setup" This reverts commit 23d7dc86d5b87a3a34df2ae69c2caacef803ef81. * fix: optimize asynchronous memory handling * fix: enhance task logging by adding size check for special cases * refactor: enhance async initialization and streamline setup process * refactor: optimize async setup by consolidating initialization tasks * chore: update changelog for Mihomo(Meta) kernel upgrade to v1.19.13 * fix: improve startup phase initialization performance * refactor: optimize file read/write performance to reduce application wait time * refactor: simplify app instance exit logic and adjust system proxy guard initialization * refactor: change resolve_setup_async to synchronous execution for improved performance * refactor: update resolve_setup_async to accept AppHandle for improved initialization flow * refactor: remove unnecessary initialization of portable flag in run function * refactor: consolidate async initialization tasks into a single blocking call for improved execution flow * refactor: optimize resolve_setup_async by restructuring async tasks for improved concurrency * refactor: streamline resolve_setup_async and embed_server for improved async handling * refactor: separate synchronous and asynchronous setup functions for improved clarity * refactor: simplify async notification handling and remove redundant network manager initialization * refactor: enhance async handling in proxy request cache and window creation logic * refactor: improve code formatting and readability in ProxyRequestCache * refactor: adjust singleton check timeout and optimize trace size conditions * refactor: update TRACE_SPECIAL_SIZE to include additional size condition * refactor: update kode-bridge dependency to version 0.2.1-rc2 * refactor: replace RwLock with AtomicBool for UI readiness and implement event-driven monitoring * refactor: convert async functions to synchronous for window management * Update src-tauri/src/utils/resolve/window.rs * fix: handle missing app_handle in create_window function * Update src-tauri/src/module/lightweight.rs * format --- src-tauri/src/cmd/proxy.rs | 76 +++++- src-tauri/src/core/tray/mod.rs | 274 +++++++++++++++++++-- src-tauri/src/lib.rs | 2 + src/components/home/current-proxy-card.tsx | 13 +- src/components/proxy/proxy-groups.tsx | 83 +++++-- src/providers/app-data-provider.tsx | 107 +++++++- src/services/cmds.ts | 8 + 7 files changed, 506 insertions(+), 57 deletions(-) diff --git a/src-tauri/src/cmd/proxy.rs b/src-tauri/src/cmd/proxy.rs index 1b72627f0..55c8e740f 100644 --- a/src-tauri/src/cmd/proxy.rs +++ b/src-tauri/src/cmd/proxy.rs @@ -1,5 +1,13 @@ +use tauri::Emitter; + use super::CmdResult; -use crate::{ipc::IpcManager, logging, state::proxy::ProxyRequestCache, utils::logging::Type}; +use crate::{ + core::{handle::Handle, tray::Tray}, + ipc::IpcManager, + logging, + state::proxy::ProxyRequestCache, + utils::logging::Type, +}; use std::time::Duration; const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(60); @@ -45,3 +53,69 @@ pub async fn get_providers_proxies() -> CmdResult { .await; Ok((*value).clone()) } + +/// 同步托盘和GUI的代理选择状态 +#[tauri::command] +pub async fn sync_tray_proxy_selection() -> CmdResult<()> { + use crate::core::tray::Tray; + + match Tray::global().update_menu().await { + Ok(_) => { + logging!(info, Type::Cmd, "Tray proxy selection synced successfully"); + Ok(()) + } + Err(e) => { + logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e}"); + Err(e.to_string()) + } + } +} + +/// 更新代理选择并同步托盘和GUI状态 +#[tauri::command] +pub async fn update_proxy_and_sync(group: String, proxy: String) -> CmdResult<()> { + match IpcManager::global().update_proxy(&group, &proxy).await { + Ok(_) => { + logging!( + info, + Type::Cmd, + "Proxy updated successfully: {} -> {}", + group, + proxy + ); + + let cache = crate::state::proxy::ProxyRequestCache::global(); + let key = crate::state::proxy::ProxyRequestCache::make_key("proxies", "default"); + cache.map.remove(&key); + + if let Err(e) = Tray::global().update_menu().await { + 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", ()); + } + + logging!( + info, + Type::Cmd, + "Proxy and sync completed successfully: {} -> {}", + group, + proxy + ); + Ok(()) + } + Err(e) => { + logging!( + error, + Type::Cmd, + "Failed to update proxy: {} -> {}, error: {}", + group, + proxy, + e + ); + Err(e.to_string()) + } + } +} diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 64445b4c6..32e099ee7 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -1,5 +1,6 @@ use once_cell::sync::OnceCell; use tauri::tray::TrayIconBuilder; +use tauri::Emitter; #[cfg(target_os = "macos")] pub mod speed_rate; use crate::ipc::Rate; @@ -7,7 +8,9 @@ use crate::process::AsyncHandler; use crate::{ cmd, config::Config, - feat, logging, + feat, + ipc::IpcManager, + logging, module::lightweight::is_in_lightweight_mode, singleton_lazy, utils::{dirs::find_target_icons, i18n::t}, @@ -281,6 +284,15 @@ impl Tray { .unwrap_or_default(); let is_lightweight_mode = is_in_lightweight_mode(); + // 获取代理节点 + let proxy_nodes_data = match IpcManager::global().get_proxies().await { + Ok(data) => data, + Err(e) => { + log::warn!(target: "app", "获取代理节点数据失败: {}", e); + serde_json::Value::Object(serde_json::Map::new()) + } + }; + match app_handle.tray_by_id("main") { Some(tray) => { let _ = tray.set_menu(Some( @@ -291,6 +303,7 @@ impl Tray { *tun_mode, profile_uid_and_name, is_lightweight_mode, + proxy_nodes_data, ) .await?, )); @@ -557,9 +570,25 @@ async fn create_tray_menu( tun_mode_enabled: bool, profile_uid_and_name: Vec<(String, String)>, is_lightweight_mode: bool, + proxy_nodes_data: serde_json::Value, ) -> Result> { let mode = mode.unwrap_or(""); + // 获取当前配置文件的选中代理组信息 + let current_profile_selected = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + if let Some(current_profile_uid) = profiles.get_current() { + if let Ok(profile) = profiles.get_item(¤t_profile_uid) { + profile.selected.clone().unwrap_or_default() + } else { + Vec::new() + } + } else { + Vec::new() + } + }; + let version = env!("CARGO_PKG_VERSION"); let hotkeys = Config::verge() @@ -606,12 +635,131 @@ async fn create_tray_menu( results.into_iter().collect::, _>>()? }; + // 代理组子菜单 + let proxy_submenus: Vec> = { + let mut submenus = Vec::new(); + + // + if let Some(proxies) = proxy_nodes_data.get("proxies").and_then(|v| v.as_object()) { + for (group_name, group_data) in proxies.iter() { + if let Some(all_proxies) = group_data.get("all").and_then(|v| v.as_array()) { + // 在全局模式下只显示GLOBAL组,在规则模式下显示所有Selector类型的代理组 + let should_show_group = if mode == "global" { + group_name == "GLOBAL" + } else { + group_name != "GLOBAL" + }; + + if !should_show_group { + continue; + } + + let now_proxy = group_data.get("now").and_then(|v| v.as_str()).unwrap_or(""); + + // 每个代理组创建子菜单项 + let mut group_items = Vec::new(); + + for proxy_name in all_proxies.iter() { + if let Some(proxy_str) = proxy_name.as_str() { + let is_selected = proxy_str == now_proxy; + let item_id = format!("proxy_{}_{}", group_name, proxy_str); + + let display_text = { + if let Some(proxy_detail) = proxies.get(proxy_str) { + if let Some(history) = + proxy_detail.get("history").and_then(|h| h.as_array()) + { + if let Some(last_record) = history.last() { + if let Some(delay) = + last_record.get("delay").and_then(|d| d.as_i64()) + { + if delay == -1 { + format!("{} | -ms", proxy_str) + } else if delay >= 10000 { + format!("{} | -1ms", proxy_str) + } else { + format!("{} | {}ms", proxy_str, delay) + } + } else { + format!("{} | -ms ", proxy_str) + } + } else { + format!("{} | -ms ", proxy_str) + } + } else { + format!("{} | -ms", proxy_str) + } + } else { + format!("{} | -ms ", proxy_str) + } + }; + + match CheckMenuItem::with_id( + app_handle, + item_id, + display_text, // 显示包含延迟的节点名 + true, + is_selected, + None::<&str>, + ) { + Ok(item) => group_items.push(item), + Err(e) => log::warn!(target: "app", "创建代理菜单项失败: {}", e), + } + } + } + + // 创建代理组子菜单 + if !group_items.is_empty() { + let group_items_refs: Vec<&dyn IsMenuItem> = group_items + .iter() + .map(|item| item as &dyn IsMenuItem) + .collect(); + + // 判断当前代理组是否为真正在使用中的组 + let is_group_active = if mode == "global" { + group_name == "GLOBAL" && !now_proxy.is_empty() + } else if mode == "direct" { + // 直连模式下 不显示任何勾选 + false + } else { + let is_user_selected = current_profile_selected + .iter() + .any(|selected| selected.name.as_deref() == Some(group_name)); + is_user_selected && !now_proxy.is_empty() + }; + + // 如果组处于活动状态,在组名前添加勾选标记 + let group_display_name = if is_group_active { + format!("✓ {}", group_name) + } else { + group_name.to_string() + }; + + match Submenu::with_id_and_items( + app_handle, + format!("proxy_group_{}", group_name), + group_display_name, // 使用带勾选标记的组名 + true, + &group_items_refs, + ) { + Ok(submenu) => submenus.push(submenu), + Err(e) => log::warn!(target: "app", "创建代理组子菜单失败: {}", e), + } + } + } + } + } + + submenus + }; + // Pre-fetch all localized strings let dashboard_text = t("Dashboard").await; let rule_mode_text = t("Rule Mode").await; let global_mode_text = t("Global Mode").await; let direct_mode_text = t("Direct Mode").await; let profiles_text = t("Profiles").await; + let proxies_text = t("Proxies").await; let system_proxy_text = t("System Proxy").await; let tun_mode_text = t("TUN Mode").await; let lightweight_mode_text = t("LightWeight Mode").await; @@ -675,6 +823,24 @@ async fn create_tray_menu( &profile_menu_items_refs, )?; + // 创建代理主菜单 + let proxies_submenu = if !proxy_submenus.is_empty() { + let proxy_submenu_refs: Vec<&dyn IsMenuItem> = proxy_submenus + .iter() + .map(|submenu| submenu as &dyn IsMenuItem) + .collect(); + + Some(Submenu::with_id_and_items( + app_handle, + "proxies", + proxies_text, + true, + &proxy_submenu_refs, + )?) + } else { + None + }; + let system_proxy = &CheckMenuItem::with_id( app_handle, "system_proxy", @@ -772,26 +938,37 @@ async fn create_tray_menu( let separator = &PredefinedMenuItem::separator(app_handle)?; + // 动态构建菜单项 + let mut menu_items: Vec<&dyn IsMenuItem> = vec![ + open_window, + separator, + rule_mode, + global_mode, + direct_mode, + separator, + profiles, + ]; + + // 如果有代理节点,添加代理节点菜单 + if let Some(ref proxies_menu) = proxies_submenu { + menu_items.push(proxies_menu); + } + + menu_items.extend_from_slice(&[ + separator, + system_proxy as &dyn IsMenuItem, + tun_mode as &dyn IsMenuItem, + separator, + lighteweight_mode as &dyn IsMenuItem, + copy_env as &dyn IsMenuItem, + open_dir as &dyn IsMenuItem, + more as &dyn IsMenuItem, + separator, + quit as &dyn IsMenuItem, + ]); + let menu = tauri::menu::MenuBuilder::new(app_handle) - .items(&[ - open_window, - separator, - rule_mode, - global_mode, - direct_mode, - separator, - profiles, - separator, - system_proxy, - tun_mode, - separator, - lighteweight_mode, - copy_env, - open_dir, - more, - separator, - quit, - ]) + .items(&menu_items) .build()?; Ok(menu) } @@ -865,6 +1042,63 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { let profile_index = &id["profiles_".len()..]; feat::toggle_proxy_profile(profile_index.into()).await; // Await async function } + id if id.starts_with("proxy_") => { + // proxy_{group_name}_{proxy_name} + let parts: Vec<&str> = id.splitn(3, '_').collect(); + + if parts.len() == 3 && parts[0] == "proxy" { + let group_name = parts[1]; + let proxy_name = parts[2]; + + let current_mode = { + Config::clash() + .await + .latest_ref() + .0 + .get("mode") + .map(|val| val.as_str().unwrap_or("rule")) + .unwrap_or("rule") + .to_owned() + }; + + match cmd::proxy::update_proxy_and_sync( + group_name.to_string(), + proxy_name.to_string(), + ) + .await + { + Ok(_) => { + log::info!(target: "app", " {} -> {} (模式: {})", group_name, proxy_name, current_mode); + } + Err(e) => { + log::error!(target: "app", " {} -> {}, 错误: {:?}", group_name, proxy_name, e); + + match IpcManager::global() + .update_proxy(group_name, proxy_name) + .await + { + Ok(_) => { + log::info!(target: "app", " {} -> {}", group_name, proxy_name); + + if let Err(e) = Tray::global().update_menu().await { + log::warn!(target: "app", "托盘菜单更新失败: {e}"); + } + + if let Some(app_handle) = handle::Handle::global().app_handle() + { + let _ = + app_handle.emit("verge://force-refresh-proxies", ()); + let _ = app_handle.emit("verge://refresh-proxy-config", ()); + } + } + Err(e2) => { + log::error!(target: "app", "托盘代理切换回退也失败: {} -> {}, 错误: {}", group_name, proxy_name, e2); + } + } + } + } + } + } _ => {} } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 65d95de70..bc28a3185 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -185,6 +185,8 @@ mod app_init { 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, cmd::apply_dns_config, cmd::check_dns_config_exists, diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx index 23622862a..b0cdf767d 100644 --- a/src/components/home/current-proxy-card.tsx +++ b/src/components/home/current-proxy-card.tsx @@ -29,7 +29,11 @@ import { } from "@mui/icons-material"; import { useNavigate } from "react-router-dom"; import { EnhancedCard } from "@/components/home/enhanced-card"; -import { updateProxy, deleteConnection } from "@/services/cmds"; +import { + updateProxy, + deleteConnection, + syncTrayProxySelection, +} from "@/services/cmds"; import delayManager from "@/services/delay"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-provider"; @@ -342,6 +346,13 @@ export const CurrentProxyCard = () => { }); } + // 同步托盘菜单状态 + try { + await syncTrayProxySelection(); + } catch (syncError) { + console.warn("Failed to sync tray proxy selection:", syncError); + } + // 延长刷新延迟时间 setTimeout(() => { refreshProxy(); diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index b2d723e63..015eb2c90 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -7,6 +7,8 @@ import { updateProxy, deleteConnection, getGroupProxyDelays, + syncTrayProxySelection, + updateProxyAndSync, } from "@/services/cmds"; import { forceRefreshProxies } from "@/services/cmds"; import { useProfiles } from "@/hooks/use-profiles"; @@ -341,37 +343,66 @@ export const ProxyGroups = (props: Props) => { if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return; const { name, now } = group; - await updateProxy(name, proxy.name); + console.log(`[ProxyGroups] GUI代理切换: ${name} -> ${proxy.name}`); - await forceRefreshProxies(); + try { + // 1. 保存到selected中 (先保存本地状态) + if (current) { + if (!current.selected) current.selected = []; - onProxies(); + const index = current.selected.findIndex( + (item) => item.name === group.name, + ); - // 断开连接 - if (verge?.auto_close_connection) { - getConnections().then(({ connections }) => { - connections.forEach((conn) => { - if (conn.chains.includes(now!)) { - deleteConnection(conn.id); - } + if (index < 0) { + current.selected.push({ name, now: proxy.name }); + } else { + current.selected[index] = { name, now: proxy.name }; + } + await patchCurrent({ selected: current.selected }); + } + + // 2. 使用统一的同步命令更新代理并同步状态 + await updateProxyAndSync(name, proxy.name); + console.log( + `[ProxyGroups] 代理和状态同步完成: ${name} -> ${proxy.name}`, + ); + + // 3. 刷新前端显示 + onProxies(); + + // 4. 断开连接 (异步处理,不影响UI更新) + if (verge?.auto_close_connection) { + getConnections().then(({ connections }) => { + connections.forEach((conn) => { + if (conn.chains.includes(now!)) { + deleteConnection(conn.id); + } + }); }); - }); + } + } catch (error) { + console.error( + `[ProxyGroups] 代理切换失败: ${name} -> ${proxy.name}`, + error, + ); + // 如果统一命令失败,回退到原来的方式 + try { + await updateProxy(name, proxy.name); + await forceRefreshProxies(); + await syncTrayProxySelection(); + onProxies(); + console.log( + `[ProxyGroups] 代理切换回退成功: ${name} -> ${proxy.name}`, + ); + } catch (fallbackError) { + console.error( + `[ProxyGroups] 代理切换回退也失败: ${name} -> ${proxy.name}`, + fallbackError, + ); + onProxies(); // 至少刷新显示 + } } - - // 保存到selected中 - if (!current) return; - if (!current.selected) current.selected = []; - - const index = current.selected.findIndex( - (item) => item.name === group.name, - ); - - if (index < 0) { - current.selected.push({ name, now: proxy.name }); - } else { - current.selected[index] = { name, now: proxy.name }; - } - await patchCurrent({ selected: current.selected }); }, ); diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx index 4962648b1..9e72ecd3f 100644 --- a/src/providers/app-data-provider.tsx +++ b/src/providers/app-data-provider.tsx @@ -221,16 +221,105 @@ export const AppDataProvider = ({ } }; - window.addEventListener( - "verge://refresh-clash-config", - handleRefreshClash, - ); + // 监听代理配置刷新事件(托盘代理切换等) + const handleRefreshProxy = () => { + const now = Date.now(); + console.log("[AppDataProvider] 代理配置刷新事件"); - return () => { - window.removeEventListener( - "verge://refresh-clash-config", - handleRefreshClash, - ); + if (now - lastUpdateTime > refreshThrottle) { + lastUpdateTime = now; + + setTimeout(() => { + refreshProxy().catch((e) => + console.warn("[AppDataProvider] 代理刷新失败:", e), + ); + }, 100); + } + }; + + // 监听强制代理刷新事件(托盘代理切换立即刷新) + const handleForceRefreshProxies = () => { + console.log("[AppDataProvider] 强制代理刷新事件"); + + // 立即刷新,无延迟,无防抖 + forceRefreshProxies() + .then(() => { + console.log("[AppDataProvider] 强制刷新代理缓存完成"); + // 强制刷新完成后,立即刷新前端显示 + return refreshProxy(); + }) + .then(() => { + console.log("[AppDataProvider] 前端代理数据刷新完成"); + }) + .catch((e) => { + console.warn("[AppDataProvider] 强制代理刷新失败:", e); + // 如果强制刷新失败,尝试普通刷新 + refreshProxy().catch((e2) => + console.warn("[AppDataProvider] 普通代理刷新也失败:", e2), + ); + }); + }; + + // 使用 Tauri 事件监听器替代 window 事件监听器 + const setupTauriListeners = async () => { + try { + const unlistenClash = await listen( + "verge://refresh-clash-config", + handleRefreshClash, + ); + const unlistenProxy = await listen( + "verge://refresh-proxy-config", + handleRefreshProxy, + ); + const unlistenForceRefresh = await listen( + "verge://force-refresh-proxies", + handleForceRefreshProxies, + ); + + return () => { + unlistenClash(); + unlistenProxy(); + unlistenForceRefresh(); + }; + } catch (error) { + console.warn("[AppDataProvider] 设置 Tauri 事件监听器失败:", error); + + // 降级到 window 事件监听器 + window.addEventListener( + "verge://refresh-clash-config", + handleRefreshClash, + ); + window.addEventListener( + "verge://refresh-proxy-config", + handleRefreshProxy, + ); + window.addEventListener( + "verge://force-refresh-proxies", + handleForceRefreshProxies, + ); + + return () => { + window.removeEventListener( + "verge://refresh-clash-config", + handleRefreshClash, + ); + window.removeEventListener( + "verge://refresh-proxy-config", + handleRefreshProxy, + ); + window.removeEventListener( + "verge://force-refresh-proxies", + handleForceRefreshProxies, + ); + }; + } + }; + + const cleanupTauriListeners = setupTauriListeners(); + + return async () => { + const cleanup = await cleanupTauriListeners; + cleanup(); }; } catch (error) { console.error("[AppDataProvider] 事件监听器设置失败:", error); diff --git a/src/services/cmds.ts b/src/services/cmds.ts index cfc0efb42..18e0c552c 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -143,6 +143,14 @@ export async function updateProxy(group: string, proxy: string) { // console.log(`[API] updateProxy 耗时: ${duration}ms`); } +export async function syncTrayProxySelection() { + return invoke("sync_tray_proxy_selection"); +} + +export async function updateProxyAndSync(group: string, proxy: string) { + return invoke("update_proxy_and_sync", { group, proxy }); +} + export async function getProxies(): Promise<{ global: IProxyGroupItem; direct: IProxyItem;