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)