mirror of
https://github.com/2dust/v2rayN.git
synced 2026-09-28 09:48:02 +03:00
* Add custom outbound support * Add test * Add inner fmt support * Full config to outbounds * Rename to `Outbound` * AI optimized * Fix * Add bind interface placeholder --------- Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
478 lines
20 KiB
C#
478 lines
20 KiB
C#
using Avalonia.Controls.Notifications;
|
|
using DialogHostAvalonia;
|
|
using v2rayN.Desktop.Base;
|
|
using v2rayN.Desktop.Common;
|
|
using v2rayN.Desktop.Manager;
|
|
|
|
namespace v2rayN.Desktop.Views;
|
|
|
|
public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|
{
|
|
private static Config _config;
|
|
private readonly SingleReplaceableDisposable _layoutBindingsDisposable = new();
|
|
private readonly WindowNotificationManager? _manager;
|
|
private CheckUpdateView? _checkUpdateView;
|
|
private BackupAndRestoreView? _backupAndRestoreView;
|
|
private bool _blCloseByUser = false;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_config = AppManager.Instance.Config;
|
|
_manager = new WindowNotificationManager(TopLevel.GetTopLevel(this)) { MaxItems = 3, Position = NotificationPosition.TopRight };
|
|
|
|
KeyDown += MainWindow_KeyDown;
|
|
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
|
|
menuPromotion.Click += MenuPromotion_Click;
|
|
menuCheckUpdate.Click += MenuCheckUpdate_Click;
|
|
btnNewUpdate.Click += MenuCheckUpdate_Click;
|
|
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
|
|
menuClose.Click += MenuClose_Click;
|
|
|
|
conTheme.Content ??= new ThemeSettingView();
|
|
|
|
this.WhenActivated(disposables =>
|
|
{
|
|
//servers
|
|
this.BindCommand(ViewModel, vm => vm.AddVmessServerCmd, v => v.menuAddVmessServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddVlessServerCmd, v => v.menuAddVlessServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddShadowsocksServerCmd, v => v.menuAddShadowsocksServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddSocksServerCmd, v => v.menuAddSocksServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddHttpServerCmd, v => v.menuAddHttpServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddTrojanServerCmd, v => v.menuAddTrojanServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddCustomOutboundServerCmd, v => v.menuAddCustomOutboundServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddProxyChainServerCmd, v => v.menuAddProxyChainServer).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.AddServerViaImageCmd, v => v.menuAddServerViaImage).DisposeWith(disposables);
|
|
|
|
//sub
|
|
this.BindCommand(ViewModel, vm => vm.SubSettingCmd, v => v.menuSubSetting).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.SubUpdateCmd, v => v.menuSubUpdate).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.SubUpdateViaProxyCmd, v => v.menuSubUpdateViaProxy).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.SubGroupUpdateCmd, v => v.menuSubGroupUpdate).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.SubGroupUpdateViaProxyCmd, v => v.menuSubGroupUpdateViaProxy).DisposeWith(disposables);
|
|
|
|
//setting
|
|
this.BindCommand(ViewModel, vm => vm.OptionSettingCmd, v => v.menuOptionSetting).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.RoutingSettingCmd, v => v.menuRoutingSetting).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.DNSSettingCmd, v => v.menuDNSSetting).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.FullConfigTemplateCmd, v => v.menuFullConfigTemplate).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.GlobalHotkeySettingCmd, v => v.menuGlobalHotkeySetting).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.RebootAsAdminCmd, v => v.menuRebootAsAdmin).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.ClearServerStatisticsCmd, v => v.menuClearServerStatistics).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.OpenTheFileLocationCmd, v => v.menuOpenTheFileLocation).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.RegionalPresetDefaultCmd, v => v.menuRegionalPresetsDefault).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.RegionalPresetRussiaCmd, v => v.menuRegionalPresetsRussia).DisposeWith(disposables);
|
|
this.BindCommand(ViewModel, vm => vm.RegionalPresetIranCmd, v => v.menuRegionalPresetsIran).DisposeWith(disposables);
|
|
|
|
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
|
|
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
|
|
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.IsVisible).DisposeWith(disposables);
|
|
|
|
this.OneWayBind(ViewModel, vm => vm.StatusBarViewModel, v => v.contentStatusBarView.Content).DisposeWith(disposables);
|
|
|
|
_layoutBindingsDisposable.DisposeWith(disposables);
|
|
|
|
this.WhenAnyValue(v => v.ViewModel.MainGirdOrientation)
|
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
|
.Subscribe(UpdateLayout)
|
|
.DisposeWith(disposables);
|
|
|
|
ViewModel.ReadTextFromClipboardInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
var result = await AvaUtils.GetClipboardData(this);
|
|
interaction.SetOutput(result);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.ScanScreenInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
ShowHideWindow(false);
|
|
await Task.Delay(200);
|
|
var result = QRCodeAvaloniaUtils.CaptureScreen();
|
|
ShowHideWindow(true);
|
|
interaction.SetOutput(result);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.BrowseImageFileInteraction.RegisterHandler(async interaction =>
|
|
{
|
|
var result = await UI.OpenFileDialog(null);
|
|
interaction.SetOutput(result);
|
|
}).DisposeWith(disposables);
|
|
|
|
ViewModel.ShowHideWindowInteraction.RegisterHandler(interaction =>
|
|
{
|
|
ShowHideWindow(interaction.Input);
|
|
interaction.SetOutput(RxVoid.Default);
|
|
}).DisposeWith(disposables);
|
|
|
|
AppEvents.SendSnackMsgRequested
|
|
.AsObservable()
|
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
|
.Subscribe(async content => await DelegateSnackMsg(content))
|
|
.DisposeWith(disposables);
|
|
|
|
AppEvents.AppExitRequested
|
|
.AsObservable()
|
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
|
.Subscribe(_ => StorageUI())
|
|
.DisposeWith(disposables);
|
|
|
|
AppEvents.ShutdownRequested
|
|
.AsObservable()
|
|
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
|
.Subscribe(Shutdown)
|
|
.DisposeWith(disposables);
|
|
});
|
|
|
|
if (Utils.IsWindows())
|
|
{
|
|
Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
|
|
|
if (!Design.IsDesignMode)
|
|
{
|
|
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
|
|
HotkeyManager.Instance.Init(_config, OnHotkeyHandler);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Title = $"{Utils.GetVersion()}";
|
|
menuAddServerViaScan.IsVisible = false;
|
|
}
|
|
|
|
if (_config.UiItem.AutoHideStartup && Utils.IsWindows())
|
|
{
|
|
WindowState = WindowState.Minimized;
|
|
}
|
|
|
|
AddHelpMenuItem();
|
|
}
|
|
|
|
#region Event
|
|
|
|
private void OnProgramStarted(object state, bool timeout)
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
ShowHideWindow(true),
|
|
DispatcherPriority.Default);
|
|
}
|
|
|
|
private async Task DelegateSnackMsg(string content)
|
|
{
|
|
_manager?.Show(new Avalonia.Controls.Notifications.Notification(null, content, NotificationType.Information));
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private void OnHotkeyHandler(EGlobalHotkey e)
|
|
{
|
|
switch (e)
|
|
{
|
|
case EGlobalHotkey.ShowForm:
|
|
Dispatcher.UIThread.Post(() => ShowHideWindow(null));
|
|
break;
|
|
|
|
case EGlobalHotkey.SystemProxyClear:
|
|
case EGlobalHotkey.SystemProxySet:
|
|
case EGlobalHotkey.SystemProxyUnchanged:
|
|
case EGlobalHotkey.SystemProxyPac:
|
|
AppEvents.SysProxyChangeRequested.Publish((ESysProxyType)((int)e - 1));
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected override async void OnClosing(WindowClosingEventArgs e)
|
|
{
|
|
if (_blCloseByUser)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Logging.SaveLog("OnClosing -> " + e.CloseReason.ToString());
|
|
|
|
switch (e.CloseReason)
|
|
{
|
|
case WindowCloseReason.OwnerWindowClosing or WindowCloseReason.WindowClosing:
|
|
e.Cancel = true;
|
|
ShowHideWindow(false);
|
|
break;
|
|
|
|
case WindowCloseReason.ApplicationShutdown or WindowCloseReason.OSShutdown:
|
|
await AppManager.Instance.AppExitAsync(false);
|
|
break;
|
|
}
|
|
|
|
base.OnClosing(e);
|
|
}
|
|
|
|
private async void MainWindow_KeyDown(object? sender, KeyEventArgs e)
|
|
{
|
|
if (e.KeyModifiers is KeyModifiers.Control or KeyModifiers.Meta)
|
|
{
|
|
switch (e.Key)
|
|
{
|
|
case Key.V:
|
|
await AddServerViaClipboardAsync();
|
|
break;
|
|
|
|
case Key.S:
|
|
await ScanScreenTaskAsync();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (e.Key == Key.F5)
|
|
{
|
|
ViewModel?.Reload();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MenuPromotion_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
ProcUtils.ProcessStart($"{Utils.Base64Decode(Global.PromotionUrl)}?t={DateTime.Now.Ticks}");
|
|
}
|
|
|
|
private void MenuSettingsSetUWP_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
|
|
}
|
|
|
|
public async Task AddServerViaClipboardAsync()
|
|
{
|
|
var clipboardData = await AvaUtils.GetClipboardData(this);
|
|
if (clipboardData.IsNotEmpty() && ViewModel != null)
|
|
{
|
|
await ViewModel.AddServerViaClipboardAsync(clipboardData);
|
|
}
|
|
}
|
|
|
|
public async Task ScanScreenTaskAsync()
|
|
{
|
|
ShowHideWindow(false);
|
|
|
|
await Task.Delay(200);
|
|
|
|
var bytes = QRCodeAvaloniaUtils.CaptureScreen();
|
|
if (bytes != null && ViewModel != null)
|
|
{
|
|
await ViewModel.ScanScreenResult(bytes);
|
|
}
|
|
|
|
ShowHideWindow(true);
|
|
}
|
|
|
|
private void MenuCheckUpdate_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
_checkUpdateView ??= new CheckUpdateView();
|
|
_checkUpdateView.ViewModel = ViewModel?.CheckUpdateViewModel;
|
|
DialogHost.Show(_checkUpdateView);
|
|
|
|
AppEvents.HasUpdateNotified.Publish(false);
|
|
}
|
|
|
|
private void MenuBackupAndRestore_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
_backupAndRestoreView ??= new BackupAndRestoreView();
|
|
_backupAndRestoreView.ViewModel = ViewModel?.BackupAndRestoreViewModel;
|
|
DialogHost.Show(_backupAndRestoreView);
|
|
}
|
|
|
|
private async void MenuClose_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (await UI.ShowYesNo(ResUI.menuExitTips) != ButtonResult.Yes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_blCloseByUser = true;
|
|
StorageUI();
|
|
|
|
await AppManager.Instance.AppExitAsync(true);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
private void Shutdown(bool obj)
|
|
{
|
|
if (obj is bool b && _blCloseByUser == false)
|
|
{
|
|
_blCloseByUser = b;
|
|
}
|
|
StorageUI();
|
|
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
{
|
|
HotkeyManager.Instance.Dispose();
|
|
desktop.Shutdown();
|
|
}
|
|
}
|
|
|
|
#endregion Event
|
|
|
|
#region UI
|
|
|
|
public void ShowHideWindow(bool? blShow)
|
|
{
|
|
var bl = blShow ??
|
|
(Utils.IsLinux() || Utils.IsMacOS()
|
|
? (!AppManager.Instance.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
|
|
: !AppManager.Instance.ShowInTaskbar);
|
|
if (bl)
|
|
{
|
|
Show();
|
|
if (WindowState == WindowState.Minimized)
|
|
{
|
|
WindowState = WindowState.Normal;
|
|
}
|
|
Activate();
|
|
Focus();
|
|
}
|
|
else
|
|
{
|
|
if (Utils.IsLinux() && _config.UiItem.Hide2TrayWhenClose == false)
|
|
{
|
|
WindowState = WindowState.Minimized;
|
|
return;
|
|
}
|
|
|
|
foreach (var ownedWindow in OwnedWindows)
|
|
{
|
|
ownedWindow.Close();
|
|
}
|
|
Hide();
|
|
}
|
|
|
|
AppManager.Instance.ShowInTaskbar = bl;
|
|
}
|
|
|
|
protected override void OnLoaded(object? sender, RoutedEventArgs e)
|
|
{
|
|
base.OnLoaded(sender, e);
|
|
if (_config.UiItem.AutoHideStartup)
|
|
{
|
|
ShowHideWindow(false);
|
|
}
|
|
RestoreUI();
|
|
}
|
|
|
|
private void RestoreUI()
|
|
{
|
|
if (_config.UiItem.MainGirdHeight1 > 0 && _config.UiItem.MainGirdHeight2 > 0)
|
|
{
|
|
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
|
|
{
|
|
gridMain.ColumnDefinitions[0].Width = new GridLength(_config.UiItem.MainGirdHeight1, GridUnitType.Star);
|
|
gridMain.ColumnDefinitions[2].Width = new GridLength(_config.UiItem.MainGirdHeight2, GridUnitType.Star);
|
|
}
|
|
else if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Vertical)
|
|
{
|
|
gridMain1.RowDefinitions[0].Height = new GridLength(_config.UiItem.MainGirdHeight1, GridUnitType.Star);
|
|
gridMain1.RowDefinitions[2].Height = new GridLength(_config.UiItem.MainGirdHeight2, GridUnitType.Star);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StorageUI()
|
|
{
|
|
ConfigHandler.SaveWindowSizeItem(_config, GetType().Name, Width, Height);
|
|
|
|
if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Horizontal)
|
|
{
|
|
ConfigHandler.SaveMainGirdHeight(_config, gridMain.ColumnDefinitions[0].ActualWidth, gridMain.ColumnDefinitions[2].ActualWidth);
|
|
}
|
|
else if (_config.UiItem.MainGirdOrientation == EGirdOrientation.Vertical)
|
|
{
|
|
ConfigHandler.SaveMainGirdHeight(_config, gridMain1.RowDefinitions[0].ActualHeight, gridMain1.RowDefinitions[2].ActualHeight);
|
|
}
|
|
}
|
|
|
|
private void UpdateLayout(EGirdOrientation orientation)
|
|
{
|
|
var currentLayoutDisposables = new MultipleDisposable();
|
|
_layoutBindingsDisposable.Create(currentLayoutDisposables);
|
|
|
|
gridMain.IsVisible = orientation == EGirdOrientation.Horizontal;
|
|
gridMain1.IsVisible = orientation == EGirdOrientation.Vertical;
|
|
gridMain2.IsVisible = orientation == EGirdOrientation.Tab;
|
|
|
|
switch (orientation)
|
|
{
|
|
case EGirdOrientation.Horizontal:
|
|
this.OneWayBind(ViewModel, vm => vm.ProfilesViewModel, v => v.tabProfiles.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabMsgView.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
|
break;
|
|
|
|
case EGirdOrientation.Vertical:
|
|
this.OneWayBind(ViewModel, vm => vm.ProfilesViewModel, v => v.tabProfiles1.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView1.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies1.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections1.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabMsgView1.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies1.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections1.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain1.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
|
break;
|
|
|
|
case EGirdOrientation.Tab:
|
|
default:
|
|
this.OneWayBind(ViewModel, vm => vm.ProfilesViewModel, v => v.tabProfiles2.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView2.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies2.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections2.Content).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies2.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections2.IsVisible).DisposeWith(currentLayoutDisposables);
|
|
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain2.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
|
break;
|
|
}
|
|
|
|
RestoreUI();
|
|
}
|
|
|
|
private void AddHelpMenuItem()
|
|
{
|
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
|
foreach (var it in coreInfo
|
|
.Where(t => t.CoreType is not ECoreType.v2fly
|
|
and not ECoreType.hysteria))
|
|
{
|
|
var item = new MenuItem()
|
|
{
|
|
Tag = it.Url?.Replace(@"/releases", ""),
|
|
Header = string.Format(ResUI.menuWebsiteItem, it.CoreType.ToString().Replace("_", " ")).UpperFirstChar()
|
|
};
|
|
item.Click += MenuItem_Click;
|
|
menuHelp.Items.Add(item);
|
|
}
|
|
}
|
|
|
|
private void MenuItem_Click(object? sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is MenuItem item)
|
|
{
|
|
ProcUtils.ProcessStart(item.Tag?.ToString());
|
|
}
|
|
}
|
|
|
|
#endregion UI
|
|
}
|