diff --git a/v2rayN/v2rayN.Desktop/App.axaml b/v2rayN/v2rayN.Desktop/App.axaml
index 21204f60..ba9ef347 100644
--- a/v2rayN/v2rayN.Desktop/App.axaml
+++ b/v2rayN/v2rayN.Desktop/App.axaml
@@ -64,6 +64,14 @@
IsVisible="{Binding BlSystemProxyPacVisible}"
ToggleType="Radio" />
+
+
+
+
+
+
diff --git a/v2rayN/v2rayN.Desktop/App.axaml.cs b/v2rayN/v2rayN.Desktop/App.axaml.cs
index 35371f0f..25c51b6b 100644
--- a/v2rayN/v2rayN.Desktop/App.axaml.cs
+++ b/v2rayN/v2rayN.Desktop/App.axaml.cs
@@ -4,6 +4,8 @@ namespace v2rayN.Desktop;
public partial class App : Application
{
+ private readonly Dictionary _routingMenuMap = [];
+
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -20,6 +22,7 @@ public partial class App : Application
{
AppManager.Instance.InitComponents();
DataContext = StatusBarViewModel.Instance;
+ InitRoutingMenu();
}
desktop.Exit += OnExit;
@@ -44,6 +47,118 @@ public partial class App : Application
private void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e)
{
+ foreach (var menuItem in _routingMenuMap.Keys)
+ {
+ menuItem.Click -= MenuRoutingClick;
+ }
+ _routingMenuMap.Clear();
+ }
+
+ private void InitRoutingMenu()
+ {
+ var vm = StatusBarViewModel.Instance;
+ vm.RoutingItems.CollectionChanged += (_, _) => RefreshRoutingMenuItems();
+ vm.PropertyChanged += (_, args) =>
+ {
+ if (args.PropertyName == nameof(StatusBarViewModel.SelectedRouting))
+ {
+ RefreshRoutingMenuCheckState();
+ }
+ };
+
+ RefreshRoutingMenuItems();
+ }
+
+ private void RefreshRoutingMenuItems()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ var routingMenu = GetRoutingRootMenu();
+ if (routingMenu == null)
+ {
+ return;
+ }
+
+ routingMenu.Menu ??= new NativeMenu();
+ foreach (var menuItem in _routingMenuMap.Keys)
+ {
+ menuItem.Click -= MenuRoutingClick;
+ }
+ _routingMenuMap.Clear();
+ routingMenu.Menu.Items.Clear();
+
+ foreach (var routing in StatusBarViewModel.Instance.RoutingItems)
+ {
+ var menuItem = new NativeMenuItem
+ {
+ Header = routing.Remarks,
+ IsChecked = routing.IsActive,
+ ToggleType = NativeMenuItemToggleType.Radio,
+ };
+ menuItem.Click += MenuRoutingClick;
+ _routingMenuMap[menuItem] = routing.Id;
+ routingMenu.Menu.Items.Add(menuItem);
+ }
+
+ RefreshRoutingMenuCheckState();
+ }, DispatcherPriority.Background);
+ }
+
+ private void RefreshRoutingMenuCheckState()
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ var selectedRoutingId = StatusBarViewModel.Instance.SelectedRouting?.Id;
+ foreach (var pair in _routingMenuMap)
+ {
+ pair.Key.IsChecked = pair.Value == selectedRoutingId;
+ }
+ }, DispatcherPriority.Background);
+ }
+
+ private NativeMenuItem? GetRoutingRootMenu()
+ {
+ if (Current == null)
+ {
+ return null;
+ }
+
+ var icons = TrayIcon.GetIcons(Current);
+ if (icons.Count == 0 || icons[0].Menu is not NativeMenu trayMenu)
+ {
+ return null;
+ }
+
+ return trayMenu.Items
+ .OfType()
+ .FirstOrDefault(x => Equals(x.Header, ResUI.menuRouting));
+ }
+
+ private void MenuRoutingClick(object? sender, EventArgs e)
+ {
+ if (sender is not NativeMenuItem menuItem)
+ {
+ return;
+ }
+
+ if (!_routingMenuMap.TryGetValue(menuItem, out var routingId))
+ {
+ return;
+ }
+
+ var target = StatusBarViewModel.Instance.RoutingItems.FirstOrDefault(x => x.Id == routingId);
+ if (target == null)
+ {
+ return;
+ }
+
+ if (StatusBarViewModel.Instance.SelectedRouting?.Id == target.Id)
+ {
+ RefreshRoutingMenuCheckState();
+ return;
+ }
+
+ StatusBarViewModel.Instance.SelectedRouting = target;
}
private async void MenuAddServerViaClipboardClick(object? sender, EventArgs e)