mirror of
https://github.com/2dust/v2rayN.git
synced 2026-04-16 12:35:46 +00:00
Add routing menu to Desktop tray icon
Add routing selection functionality to the system tray menu for macOS/Linux (Avalonia Desktop), bringing feature parity with the Windows version which already has this capability. - Add dynamic routing items to NativeMenu in App.axaml - Implement menu item click handling and state refresh in App.axaml.cs - Support radio-style selection for routing options Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
parent
661affd6a5
commit
01a5805d98
2 changed files with 123 additions and 0 deletions
|
|
@ -64,6 +64,14 @@
|
|||
IsVisible="{Binding BlSystemProxyPacVisible}"
|
||||
ToggleType="Radio" />
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem
|
||||
Header="{x:Static resx:ResUI.menuRouting}"
|
||||
IsVisible="{Binding BlRouting}">
|
||||
<NativeMenuItem.Menu>
|
||||
<NativeMenu />
|
||||
</NativeMenuItem.Menu>
|
||||
</NativeMenuItem>
|
||||
<NativeMenuItemSeparator />
|
||||
<NativeMenuItem Click="MenuAddServerViaClipboardClick" Header="{x:Static resx:ResUI.menuAddServerViaClipboard}" />
|
||||
<NativeMenuItem Command="{Binding SubUpdateCmd}" Header="{x:Static resx:ResUI.menuSubUpdate}" />
|
||||
<NativeMenuItem Command="{Binding SubUpdateViaProxyCmd}" Header="{x:Static resx:ResUI.menuSubUpdateViaProxy}" />
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ namespace v2rayN.Desktop;
|
|||
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly Dictionary<NativeMenuItem, string> _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<NativeMenuItem>()
|
||||
.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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue