diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index 68b521da..88b012eb 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -198,6 +198,9 @@ pub struct IVerge { pub enable_tray_icon: Option, + /// show proxy groups directly on tray root menu + pub tray_inline_proxy_groups: Option, + /// 自动进入轻量模式 pub enable_auto_light_weight_mode: Option, @@ -398,6 +401,7 @@ impl IVerge { webdav_password: None, enable_tray_speed: Some(false), enable_tray_icon: Some(true), + tray_inline_proxy_groups: Some(false), enable_global_hotkey: Some(true), enable_auto_light_weight_mode: Some(false), auto_light_weight_minutes: Some(10), @@ -489,6 +493,7 @@ impl IVerge { patch!(webdav_password); patch!(enable_tray_speed); patch!(enable_tray_icon); + patch!(tray_inline_proxy_groups); patch!(enable_auto_light_weight_mode); patch!(auto_light_weight_minutes); patch!(enable_dns_settings); @@ -585,6 +590,7 @@ pub struct IVergeResponse { pub webdav_password: Option, pub enable_tray_speed: Option, pub enable_tray_icon: Option, + pub tray_inline_proxy_groups: Option, pub enable_auto_light_weight_mode: Option, pub auto_light_weight_minutes: Option, pub enable_dns_settings: Option, @@ -658,6 +664,7 @@ impl From for IVergeResponse { webdav_password: verge.webdav_password, enable_tray_speed: verge.enable_tray_speed, enable_tray_icon: verge.enable_tray_icon, + tray_inline_proxy_groups: verge.tray_inline_proxy_groups, enable_auto_light_weight_mode: verge.enable_auto_light_weight_mode, auto_light_weight_minutes: verge.auto_light_weight_minutes, enable_dns_settings: verge.enable_dns_settings, diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index bc6a6f4f..3be829db 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -605,11 +605,46 @@ async fn create_tray_menu( let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await; + let runtime_proxy_groups_order = cmd::get_runtime_config() + .await + .map_err(|e| { + logging!( + error, + Type::Cmd, + "Failed to fetch runtime proxy groups for tray menu: {e}" + ); + }) + .ok() + .flatten() + .map(|config| { + config + .get("proxy-groups") + .and_then(|groups| groups.as_sequence()) + .map(|groups| { + groups + .iter() + .filter_map(|group| group.get("name")) + .filter_map(|name| name.as_str()) + .map(|name| name.to_string()) + .collect::>() + }) + .unwrap_or_default() + }); + + let proxy_group_order_map = runtime_proxy_groups_order.as_ref().map(|group_names| { + group_names + .iter() + .enumerate() + .map(|(index, name)| (name.clone(), index)) + .collect::>() + }); + + let verge_settings = Config::verge().await.latest_ref().clone(); + let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false); + let version = env!("CARGO_PKG_VERSION"); - let hotkeys = Config::verge() - .await - .latest_ref() + let hotkeys = verge_settings .hotkeys .as_ref() .map(|h| { @@ -653,8 +688,7 @@ async fn create_tray_menu( // 代理组子菜单 let proxy_submenus: Vec> = { - let mut submenus = Vec::new(); - let mut group_name_submenus_hash = HashMap::new(); + let mut submenus: Vec<(String, usize, Submenu)> = Vec::new(); // TODO: 应用启动时,内核还未启动完全,无法获取代理节点信息 if let Ok(proxy_nodes_data) = proxy_nodes_data { @@ -745,53 +779,32 @@ async fn create_tray_menu( true, &group_items_refs, ) { - group_name_submenus_hash.insert(group_name.to_string(), submenu); + let insertion_index = submenus.len(); + submenus.push((group_name.to_string(), insertion_index, submenu)); } else { log::warn!(target: "app", "创建代理组子菜单失败: {}", group_name); } } } - // 获取运行时代理组配置 - let runtime_proxy_groups_config = cmd::get_runtime_config() - .await - .map_err(|e| { - logging!( - error, - Type::Cmd, - "Failed to fetch runtime proxy groups for tray menu: {e}" - ); - }) - .ok() - .flatten() - .map(|config| { - config - .get("proxy-groups") - .and_then(|groups| groups.as_sequence()) - .map(|groups| { - groups - .iter() - .filter_map(|group| group.get("name")) - .filter_map(|name| name.as_str()) - .map(|name| name.into()) - .collect::>() - }) - .unwrap_or_default() - }); - - if let Some(runtime_proxy_groups_config) = runtime_proxy_groups_config { - for group_name in runtime_proxy_groups_config { - if let Some(submenu) = group_name_submenus_hash.get(&group_name) { - submenus.push(submenu.clone()); - } - } - } else { - for (_, submenu) in group_name_submenus_hash { - submenus.push(submenu); - } + if let Some(order_map) = proxy_group_order_map.as_ref() { + submenus.sort_by( + |(name_a, original_index_a, _), (name_b, original_index_b, _)| match ( + order_map.get(name_a), + order_map.get(name_b), + ) { + (Some(index_a), Some(index_b)) => index_a.cmp(index_b), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => original_index_a.cmp(original_index_b), + }, + ); } submenus + .into_iter() + .map(|(_, _, submenu)| submenu) + .collect() }; // Pre-fetch all localized strings @@ -865,22 +878,34 @@ async fn create_tray_menu( )?; // 创建代理主菜单 - 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(); + let (proxies_submenu, inline_proxy_items): (Option>, Vec<&dyn IsMenuItem>) = + if show_proxy_groups_inline { + ( + None, + proxy_submenus + .iter() + .map(|submenu| submenu as &dyn IsMenuItem) + .collect(), + ) + } else 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 - }; + ( + Some(Submenu::with_id_and_items( + app_handle, + "proxies", + proxies_text, + true, + &proxy_submenu_refs, + )?), + Vec::new(), + ) + } else { + (None, Vec::new()) + }; let system_proxy = &CheckMenuItem::with_id( app_handle, @@ -991,7 +1016,11 @@ async fn create_tray_menu( ]; // 如果有代理节点,添加代理节点菜单 - if let Some(ref proxies_menu) = proxies_submenu { + if show_proxy_groups_inline { + if !inline_proxy_items.is_empty() { + menu_items.extend_from_slice(&inline_proxy_items); + } + } else if let Some(ref proxies_menu) = proxies_submenu { menu_items.push(proxies_menu); } diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx index ad28ae3d..3b7333c4 100644 --- a/src/components/setting/mods/layout-viewer.tsx +++ b/src/components/setting/mods/layout-viewer.tsx @@ -22,6 +22,7 @@ import { useWindowDecorations } from "@/hooks/use-window"; import { copyIconFile, getAppDir } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import getSystem from "@/utils/get-system"; + import { GuardState } from "./guard-state"; const OS = getSystem(); @@ -263,6 +264,19 @@ export const LayoutViewer = forwardRef((props, ref) => { )} + + + onChangeData({ tray_inline_proxy_groups: e })} + onGuard={(e) => patchVerge({ tray_inline_proxy_groups: e })} + > + + + diff --git a/src/locales/en.json b/src/locales/en.json index fc885942..782639c1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -512,6 +512,7 @@ "Direct Mode": "Direct Mode", "Enable Tray Speed": "Enable Tray Speed", "Enable Tray Icon": "Enable Tray Icon", + "Show Proxy Groups Inline": "Show Proxy Groups Inline", "LightWeight Mode": "Lightweight Mode", "LightWeight Mode Info": "Close the GUI and keep only the kernel running", "LightWeight Mode Settings": "LightWeight Mode Settings", diff --git a/src/locales/zh.json b/src/locales/zh.json index 98d900d5..af34f4f0 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -512,6 +512,7 @@ "Direct Mode": "直连模式", "Enable Tray Speed": "启用托盘速率", "Enable Tray Icon": "启用托盘图标", + "Show Proxy Groups Inline": "将代理组显示在托盘一级菜单", "LightWeight Mode": "轻量模式", "LightWeight Mode Info": "关闭GUI界面,仅保留内核运行", "LightWeight Mode Settings": "轻量模式设置", diff --git a/src/locales/zhtw.json b/src/locales/zhtw.json index ece171b9..a90c2646 100644 --- a/src/locales/zhtw.json +++ b/src/locales/zhtw.json @@ -437,6 +437,7 @@ "Direct Mode": "直連模式", "Enable Tray Speed": "啟用托盤速率", "Enable Tray Icon": "啟用托盤圖標", + "Show Proxy Groups Inline": "將代理組顯示在托盤一級選單", "LightWeight Mode": "輕量模式", "LightWeight Mode Info": "關閉GUI界面,僅保留內核運行", "LightWeight Mode Settings": "輕量模式設置", diff --git a/src/services/types.d.ts b/src/services/types.d.ts index a34764d4..f0257895 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -802,6 +802,7 @@ interface IVergeConfig { tun_tray_icon?: boolean; enable_tray_speed?: boolean; enable_tray_icon?: boolean; + tray_inline_proxy_groups?: boolean; enable_tun_mode?: boolean; enable_auto_light_weight_mode?: boolean; auto_light_weight_minutes?: number;