feat: tray enhance (#5058)

* feat: proxy group sorting for tray

* feat(tray): add inline proxy groups toggle
This commit is contained in:
Sline
2025-10-14 17:03:37 +08:00
committed by GitHub
Unverified
parent 98527d5038
commit f541464ff4
7 changed files with 113 additions and 59 deletions

View File

@@ -198,6 +198,9 @@ pub struct IVerge {
pub enable_tray_icon: Option<bool>,
/// show proxy groups directly on tray root menu
pub tray_inline_proxy_groups: Option<bool>,
/// 自动进入轻量模式
pub enable_auto_light_weight_mode: Option<bool>,
@@ -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<String>,
pub enable_tray_speed: Option<bool>,
pub enable_tray_icon: Option<bool>,
pub tray_inline_proxy_groups: Option<bool>,
pub enable_auto_light_weight_mode: Option<bool>,
pub auto_light_weight_minutes: Option<u64>,
pub enable_dns_settings: Option<bool>,
@@ -658,6 +664,7 @@ impl From<IVerge> 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,

View File

@@ -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::<Vec<String>>()
})
.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::<HashMap<String, usize>>()
});
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<Submenu<Wry>> = {
let mut submenus = Vec::new();
let mut group_name_submenus_hash = HashMap::new();
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = 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::<Vec<String>>()
})
.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<Wry>> = proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.collect();
let (proxies_submenu, inline_proxy_items): (Option<Submenu<Wry>>, Vec<&dyn IsMenuItem<Wry>>) =
if show_proxy_groups_inline {
(
None,
proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.collect(),
)
} else if !proxy_submenus.is_empty() {
let proxy_submenu_refs: Vec<&dyn IsMenuItem<Wry>> = proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.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);
}

View File

@@ -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<DialogRef>((props, ref) => {
</GuardState>
</Item>
)}
<Item>
<ListItemText primary={t("Show Proxy Groups Inline")} />
<GuardState
value={verge?.tray_inline_proxy_groups ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => onChangeData({ tray_inline_proxy_groups: e })}
onGuard={(e) => patchVerge({ tray_inline_proxy_groups: e })}
>
<Switch edge="end" />
</GuardState>
</Item>
<Item>
<ListItemText primary={t("Common Tray Icon")} />

View File

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

View File

@@ -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": "轻量模式设置",

View File

@@ -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": "輕量模式設置",

View File

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