diff --git a/v2rayN/v2rayN.Desktop/App.axaml b/v2rayN/v2rayN.Desktop/App.axaml new file mode 100644 index 00000000..d326e4c0 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/App.axaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/App.axaml.cs b/v2rayN/v2rayN.Desktop/App.axaml.cs new file mode 100644 index 00000000..5619e7e0 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/App.axaml.cs @@ -0,0 +1,121 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Splat; +using v2rayN.Desktop.Common; +using v2rayN.Desktop.Views; + +namespace v2rayN.Desktop; + +public partial class App : Application +{ + public static EventWaitHandle ProgramStarted; + private static Config _config; + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + OnStartup(desktop.Args); + + desktop.Exit += OnExit; + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } + + private void OnStartup(string[]? Args) + { + var exePathKey = Utils.GetMD5(Utils.GetExePath()); + + var rebootas = (Args ?? new string[] { }).Any(t => t == Global.RebootAs); + //ProgramStarted = new EventWaitHandle(false, EventResetMode.AutoReset, exePathKey, out bool bCreatedNew); + //if (!rebootas && !bCreatedNew) + //{ + // ProgramStarted.Set(); + // Environment.Exit(0); + // return; + //} + + Logging.Setup(); + Init(); + Logging.LoggingEnabled(_config.guiItem.enableLog); + Logging.SaveLog($"v2rayN start up | {Utils.GetVersion()} | {Utils.GetExePath()}"); + Logging.SaveLog($"{Environment.OSVersion} - {(Environment.Is64BitOperatingSystem ? 64 : 32)}"); + Logging.ClearLogs(); + + Thread.CurrentThread.CurrentUICulture = new(_config.uiItem.currentLanguage); + } + + private void Init() + { + if (ConfigHandler.LoadConfig(ref _config) != 0) + { + Logging.SaveLog($"Loading GUI configuration file is abnormal,please restart the application{Environment.NewLine}¼ÓÔØGUIÅäÖÃÎļþÒì³£,ÇëÖØÆôÓ¦ÓÃ"); + Environment.Exit(0); + return; + } + LazyConfig.Instance.SetConfig(_config); + Locator.CurrentMutable.RegisterLazySingleton(() => new NoticeHandler(), typeof(NoticeHandler)); + + //Under Win10 + if (Utils.IsWindows() && Environment.OSVersion.Version.Major < 10) + { + Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "0", EnvironmentVariableTarget.User); + } + } + + private void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e) + { + } + + private void TrayIcon_Clicked(object? sender, EventArgs e) + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (desktop.MainWindow.IsVisible) + { + desktop.MainWindow?.Hide(); + } + else + { + desktop.MainWindow?.Show(); + } + } + } + + private void MenuAddServerViaClipboardClick(object? sender, EventArgs e) + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var clipboardData = AvaUtils.GetClipboardData(desktop.MainWindow).Result; + Locator.Current.GetService()?.AddServerViaClipboardAsync(clipboardData); + } + } + + private void MenuSubUpdate_Click(object? sender, EventArgs e) + { + Locator.Current.GetService()?.UpdateSubscriptionProcess("", false); + } + + private void MenuSubUpdateViaProxy_Click(object? sender, EventArgs e) + { + Locator.Current.GetService()?.UpdateSubscriptionProcess("", true); + } + + private void MenuExit_Click(object? sender, EventArgs e) + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + Locator.Current.GetService()?.MyAppExitAsync(false); + + desktop.Shutdown(); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Assets/NotifyIcon1.ico b/v2rayN/v2rayN.Desktop/Assets/NotifyIcon1.ico new file mode 100644 index 00000000..a978e0a8 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/NotifyIcon1.ico differ diff --git a/v2rayN/v2rayN.Desktop/Assets/NotifyIcon2.ico b/v2rayN/v2rayN.Desktop/Assets/NotifyIcon2.ico new file mode 100644 index 00000000..b625aa8e Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/NotifyIcon2.ico differ diff --git a/v2rayN/v2rayN.Desktop/Assets/NotifyIcon3.ico b/v2rayN/v2rayN.Desktop/Assets/NotifyIcon3.ico new file mode 100644 index 00000000..6b6db8e6 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/NotifyIcon3.ico differ diff --git a/v2rayN/v2rayN.Desktop/Assets/add.png b/v2rayN/v2rayN.Desktop/Assets/add.png new file mode 100644 index 00000000..41170fbe Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/add.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/close.png b/v2rayN/v2rayN.Desktop/Assets/close.png new file mode 100644 index 00000000..7f25fe11 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/close.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/copy.png b/v2rayN/v2rayN.Desktop/Assets/copy.png new file mode 100644 index 00000000..b876b511 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/copy.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/delete.png b/v2rayN/v2rayN.Desktop/Assets/delete.png new file mode 100644 index 00000000..cdcfe3b0 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/delete.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/edit.png b/v2rayN/v2rayN.Desktop/Assets/edit.png new file mode 100644 index 00000000..f269f777 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/edit.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/fit.png b/v2rayN/v2rayN.Desktop/Assets/fit.png new file mode 100644 index 00000000..e215fbf6 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/fit.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/light.png b/v2rayN/v2rayN.Desktop/Assets/light.png new file mode 100644 index 00000000..8a9c5d09 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/light.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/more.png b/v2rayN/v2rayN.Desktop/Assets/more.png new file mode 100644 index 00000000..93cb6e81 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/more.png differ diff --git a/v2rayN/v2rayN.Desktop/Assets/refresh.png b/v2rayN/v2rayN.Desktop/Assets/refresh.png new file mode 100644 index 00000000..88a9dbf1 Binary files /dev/null and b/v2rayN/v2rayN.Desktop/Assets/refresh.png differ diff --git a/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs b/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs new file mode 100644 index 00000000..5b266748 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Common/AvaUtils.cs @@ -0,0 +1,64 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using System.Reflection; + +namespace v2rayN.Desktop.Common +{ + internal class AvaUtils + { + public static async Task GetClipboardData(Window owner) + { + try + { + var clipboard = TopLevel.GetTopLevel(owner)?.Clipboard; + if (clipboard == null) return null; + return await clipboard.GetTextAsync(); + } + catch + { + return null; + } + } + + public static async Task SetClipboardData(Visual? visual, string strData) + { + try + { + var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard; + if (clipboard == null) return; + var dataObject = new DataObject(); + dataObject.Set(DataFormats.Text, strData); + await clipboard.SetDataObjectAsync(dataObject); + } + catch + { + } + } + + public static WindowIcon GetAppIcon(ESysProxyType sysProxyType) + { + int index = 1; + switch (sysProxyType) + { + case ESysProxyType.ForcedClear: + index = 1; + break; + + case ESysProxyType.ForcedChange: + case ESysProxyType.Pac: + index = 2; + break; + + case ESysProxyType.Unchanged: + index = 3; + break; + } + var uri = new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/NotifyIcon{index}.ico"); + using var bitmap = new Bitmap(AssetLoader.Open(uri)); + return new(bitmap); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Common/UI.cs b/v2rayN/v2rayN.Desktop/Common/UI.cs new file mode 100644 index 00000000..0f72c55a --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Common/UI.cs @@ -0,0 +1,50 @@ +using Avalonia.Controls; +using Avalonia.Platform.Storage; +using MsBox.Avalonia; +using MsBox.Avalonia.Enums; + +namespace v2rayN.Desktop.Common +{ + internal class UI + { + private static readonly string caption = Global.AppName; + + public static async Task ShowYesNo(Window owner, string msg) + { + var box = MessageBoxManager.GetMessageBoxStandard(caption, msg, ButtonEnum.YesNo); + return await box.ShowWindowDialogAsync(owner); + } + + public static async Task OpenFileDialog(Window owner, FilePickerFileType? filter) + { + var topLevel = TopLevel.GetTopLevel(owner); + if (topLevel == null) + { + return null; + } + // Start async operation to open the dialog. + var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = filter is null ? [FilePickerFileTypes.All, FilePickerFileTypes.ImagePng] : [filter] + }); + + return files.FirstOrDefault()?.TryGetLocalPath(); + } + + public static async Task SaveFileDialog(Window owner, string filter) + { + var topLevel = TopLevel.GetTopLevel(owner); + if (topLevel == null) + { + return null; + } + // Start async operation to open the dialog. + var files = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions + { + }); + + return files?.TryGetLocalPath(); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs b/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs new file mode 100644 index 00000000..e61425f5 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Converters/DelayColorConverter.cs @@ -0,0 +1,26 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; +using System.Globalization; + +namespace v2rayN.Desktop.Converters +{ + public class DelayColorConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + int.TryParse(value?.ToString(), out var delay); + + if (delay <= 0) + return new SolidColorBrush(Colors.Red); + if (delay <= 500) + return new SolidColorBrush(Colors.Green); + else + return new SolidColorBrush(Colors.IndianRed); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return null; + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/FodyWeavers.xml b/v2rayN/v2rayN.Desktop/FodyWeavers.xml new file mode 100644 index 00000000..63fc1484 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/GlobalUsings.cs b/v2rayN/v2rayN.Desktop/GlobalUsings.cs new file mode 100644 index 00000000..bc789ab0 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/GlobalUsings.cs @@ -0,0 +1,8 @@ +global using ServiceLib; +global using ServiceLib.Base; +global using ServiceLib.Common; +global using ServiceLib.Enums; +global using ServiceLib.Handler; +global using ServiceLib.Models; +global using ServiceLib.Resx; +global using ServiceLib.ViewModels; \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Handler/SysProxyHandler.cs b/v2rayN/v2rayN.Desktop/Handler/SysProxyHandler.cs new file mode 100644 index 00000000..55e2082d --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Handler/SysProxyHandler.cs @@ -0,0 +1,61 @@ +namespace v2rayN.Desktop.Handler +{ + public static class SysProxyHandler + { + public static async Task UpdateSysProxy(Config config, bool forceDisable) + { + var type = config.systemProxyItem.sysProxyType; + + if (forceDisable && type != ESysProxyType.Unchanged) + { + type = ESysProxyType.ForcedClear; + } + + try + { + int port = LazyConfig.Instance.GetLocalPort(EInboundProtocol.http); + if (port <= 0) + { + return false; + } + if (type == ESysProxyType.ForcedChange) + { + var strProxy = $"{Global.Loopback}:{port}"; + await SetProxy(strProxy); + } + else if (type == ESysProxyType.ForcedClear) + { + await UnsetProxy(); + } + else if (type == ESysProxyType.Unchanged) + { + } + } + catch (Exception ex) + { + Logging.SaveLog(ex.Message, ex); + } + return true; + } + + private static async Task SetProxy(string? strProxy) + { + await Task.Run(() => + { + var httpProxy = strProxy is null ? null : $"{Global.HttpProtocol}{strProxy}"; + var socksProxy = strProxy is null ? null : $"{Global.SocksProtocol}{strProxy}"; + var noProxy = $"localhost,127.0.0.0/8,::1"; + + Environment.SetEnvironmentVariable("http_proxy", httpProxy, EnvironmentVariableTarget.User); + Environment.SetEnvironmentVariable("https_proxy", httpProxy, EnvironmentVariableTarget.User); + Environment.SetEnvironmentVariable("all_proxy", socksProxy, EnvironmentVariableTarget.User); + Environment.SetEnvironmentVariable("no_proxy", noProxy, EnvironmentVariableTarget.User); + }); + } + + private static async Task UnsetProxy() + { + await SetProxy(null); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Program.cs b/v2rayN/v2rayN.Desktop/Program.cs new file mode 100644 index 00000000..3149aab0 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Program.cs @@ -0,0 +1,22 @@ +using Avalonia; +using Avalonia.ReactiveUI; + +namespace v2rayN.Desktop; + +internal class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Styles/GlobalStyles.axaml b/v2rayN/v2rayN.Desktop/Styles/GlobalStyles.axaml new file mode 100644 index 00000000..ca5f9081 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Styles/GlobalStyles.axaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs b/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs new file mode 100644 index 00000000..0794c1b9 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/ViewModels/ThemeSettingViewModel.cs @@ -0,0 +1,143 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Styling; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using Splat; +using System.Reactive.Linq; + +namespace v2rayN.Desktop.ViewModels +{ + public class ThemeSettingViewModel : MyReactiveObject + { + [Reactive] + public bool ColorModeDark { get; set; } + + [Reactive] + public int CurrentFontSize { get; set; } + + [Reactive] + public string CurrentLanguage { get; set; } + + public ThemeSettingViewModel() + { + _config = LazyConfig.Instance.Config; + _noticeHandler = Locator.Current.GetService(); + + BindingUI(); + RestoreUI(); + } + + private void RestoreUI() + { + ModifyTheme(_config.uiItem.colorModeDark); + } + + private void BindingUI() + { + ColorModeDark = _config.uiItem.colorModeDark; + CurrentFontSize = _config.uiItem.currentFontSize; + CurrentLanguage = _config.uiItem.currentLanguage; + + this.WhenAnyValue(x => x.ColorModeDark) + .Subscribe(c => + { + if (_config.uiItem.colorModeDark != ColorModeDark) + { + _config.uiItem.colorModeDark = ColorModeDark; + ModifyTheme(ColorModeDark); + ConfigHandler.SaveConfig(_config); + } + }); + + this.WhenAnyValue( + x => x.CurrentFontSize, + y => y > 0) + .Subscribe(c => + { + if (CurrentFontSize >= Global.MinFontSize) + { + _config.uiItem.currentFontSize = CurrentFontSize; + double size = CurrentFontSize; + ModifyFontSize(size); + + ConfigHandler.SaveConfig(_config); + } + }); + + this.WhenAnyValue( + x => x.CurrentLanguage, + y => y != null && !y.IsNullOrEmpty()) + .Subscribe(c => + { + if (!Utils.IsNullOrEmpty(CurrentLanguage) && _config.uiItem.currentLanguage != CurrentLanguage) + { + _config.uiItem.currentLanguage = CurrentLanguage; + Thread.CurrentThread.CurrentUICulture = new(CurrentLanguage); + ConfigHandler.SaveConfig(_config); + _noticeHandler?.Enqueue(ResUI.NeedRebootTips); + } + }); + } + + private void ModifyTheme(bool isDarkTheme) + { + var app = Application.Current; + if (app is not null) + { + app.RequestedThemeVariant = isDarkTheme ? ThemeVariant.Dark : ThemeVariant.Light; + } + } + + private void ModifyFontSize(double size) + { + Style buttonStyle = new(x => x.OfType + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs new file mode 100644 index 00000000..5379ec4d --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/ClashConnectionsView.axaml.cs @@ -0,0 +1,50 @@ +using Avalonia.Interactivity; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using ReactiveUI; +using System.Reactive.Disposables; + +namespace v2rayN.Desktop.Views +{ + public partial class ClashConnectionsView : ReactiveUserControl + { + public ClashConnectionsView() + { + InitializeComponent(); + ViewModel = new ClashConnectionsViewModel(UpdateViewHandler); + + this.WhenActivated(disposables => + { + this.OneWayBind(ViewModel, vm => vm.ConnectionItems, v => v.lstConnections.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource, v => v.lstConnections.SelectedItem).DisposeWith(disposables); + + this.BindCommand(ViewModel, vm => vm.ConnectionCloseCmd, v => v.menuConnectionClose).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.menuConnectionCloseAll).DisposeWith(disposables); + + this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); + }); + } + + private async Task UpdateViewHandler(EViewAction action, object? obj) + { + switch (action) + { + case EViewAction.DispatcherRefreshConnections: + if (obj is null) return false; + Dispatcher.UIThread.Post(() => + ViewModel?.RefreshConnections((List?)obj), + DispatcherPriority.Default); + break; + } + + return await Task.FromResult(true); + } + + private void btnClose_Click(object? sender, RoutedEventArgs e) + { + ViewModel?.ClashConnectionClose(false); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/ClashProxiesView.axaml b/v2rayN/v2rayN.Desktop/Views/ClashProxiesView.axaml new file mode 100644 index 00000000..0399ea16 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/ClashProxiesView.axaml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/ClashProxiesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ClashProxiesView.axaml.cs new file mode 100644 index 00000000..d46a2b2b --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/ClashProxiesView.axaml.cs @@ -0,0 +1,81 @@ +using Avalonia.Input; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using DynamicData; +using ReactiveUI; +using Splat; +using System.Reactive.Disposables; + +namespace v2rayN.Desktop.Views +{ + public partial class ClashProxiesView : ReactiveUserControl + { + public ClashProxiesView() + { + InitializeComponent(); + ViewModel = new ClashProxiesViewModel(UpdateViewHandler); + Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ClashProxiesViewModel)); + lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped; + this.KeyDown += ClashProxiesView_KeyDown; + + this.WhenActivated(disposables => + { + this.OneWayBind(ViewModel, vm => vm.ProxyGroups, v => v.lstProxyGroups.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedGroup, v => v.lstProxyGroups.SelectedItem).DisposeWith(disposables); + + this.OneWayBind(ViewModel, vm => vm.ProxyDetails, v => v.lstProxyDetails.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedDetail, v => v.lstProxyDetails.SelectedItem).DisposeWith(disposables); + + this.BindCommand(ViewModel, vm => vm.ProxiesReloadCmd, v => v.menuProxiesReload).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.ProxiesDelaytestCmd, v => v.menuProxiesDelaytest).DisposeWith(disposables); + + this.BindCommand(ViewModel, vm => vm.ProxiesDelaytestPartCmd, v => v.menuProxiesDelaytestPart).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.ProxiesSelectActivityCmd, v => v.menuProxiesSelectActivity).DisposeWith(disposables); + + this.Bind(ViewModel, vm => vm.RuleModeSelected, v => v.cmbRulemode.SelectedIndex).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SortingSelected, v => v.cmbSorting.SelectedIndex).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); + }); + } + + private async Task UpdateViewHandler(EViewAction action, object? obj) + { + switch (action) + { + case EViewAction.DispatcherRefreshProxyGroups: + Dispatcher.UIThread.Post(() => + ViewModel?.RefreshProxyGroups(), + DispatcherPriority.Default); + break; + + case EViewAction.DispatcherProxiesDelayTest: + if (obj is null) return false; + Dispatcher.UIThread.Post(() => + ViewModel?.ProxiesDelayTestResult((SpeedTestResult)obj), + DispatcherPriority.Default); + break; + } + + return await Task.FromResult(true); + } + + private void ClashProxiesView_KeyDown(object? sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.F5: + ViewModel?.ProxiesReload(); + break; + + case Key.Enter: + ViewModel?.SetActiveProxy(); + break; + } + } + + private void LstProxyDetails_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + ViewModel?.SetActiveProxy(); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml new file mode 100644 index 00000000..dda0ad4e --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs new file mode 100644 index 00000000..a99647f5 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -0,0 +1,127 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using ReactiveUI; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; +using v2rayN.Desktop.Common; + +namespace v2rayN.Desktop.Views +{ + public partial class MsgView : UserControl + { + private static Config? _config; + + private string lastMsgFilter = string.Empty; + private bool lastMsgFilterNotAvailable; + private ConcurrentBag _lstMsg = []; + + public MsgView() + { + InitializeComponent(); + _config = LazyConfig.Instance.Config; + MessageBus.Current.Listen(Global.CommandSendMsgView).Subscribe(x => DelegateAppendText(x)); + //Global.PresetMsgFilters.ForEach(it => + //{ + // cmbMsgFilter.Items.Add(it); + //}); + if (!_config.uiItem.mainMsgFilter.IsNullOrEmpty()) + { + cmbMsgFilter.Text = _config.uiItem.mainMsgFilter; + } + cmbMsgFilter.TextChanged += (s, e) => + { + _config.uiItem.mainMsgFilter = cmbMsgFilter.Text?.ToString(); + }; + } + + private void DelegateAppendText(string msg) + { + Dispatcher.UIThread.Post(() => AppendText(msg), DispatcherPriority.ApplicationIdle); + } + + public void AppendText(string msg) + { + if (msg == Global.CommandClearMsg) + { + ClearMsg(); + return; + } + if (togAutoRefresh.IsChecked == false) + { + return; + } + + var MsgFilter = cmbMsgFilter.Text?.ToString(); + if (MsgFilter != lastMsgFilter) lastMsgFilterNotAvailable = false; + if (!Utils.IsNullOrEmpty(MsgFilter) && !lastMsgFilterNotAvailable) + { + try + { + if (!Regex.IsMatch(msg, MsgFilter)) + { + return; + } + } + catch (Exception) + { + lastMsgFilterNotAvailable = true; + } + } + lastMsgFilter = MsgFilter; + + ShowMsg(msg); + + if (togScrollToEnd.IsChecked ?? true) + { + } + } + + private void ShowMsg(string msg) + { + if (_lstMsg.Count > 999) + { + ClearMsg(); + } + if (!msg.EndsWith(Environment.NewLine)) + { + _lstMsg.Add(Environment.NewLine); + } + _lstMsg.Add(msg); + // if (!msg.EndsWith(Environment.NewLine)) + // { + // _lstMsg.Add(Environment.NewLine); + // } + this.txtMsg.Text = string.Join("", _lstMsg); + } + + public void ClearMsg() + { + _lstMsg.Clear(); + txtMsg.Clear(); + } + + private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) + { + txtMsg.Focus(); + txtMsg.SelectAll(); + } + + private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e) + { + var data = txtMsg.SelectedText.TrimEx(); + await AvaUtils.SetClipboardData(this, data); + } + + private async void menuMsgViewCopyAll_Click(object? sender, RoutedEventArgs e) + { + var data = txtMsg.Text.TrimEx(); + await AvaUtils.SetClipboardData(this, data); + } + + private void menuMsgViewClear_Click(object? sender, RoutedEventArgs e) + { + ClearMsg(); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml new file mode 100644 index 00000000..931c85a7 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -0,0 +1,857 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs new file mode 100644 index 00000000..280a13b7 --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs @@ -0,0 +1,468 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using MsBox.Avalonia.Enums; +using ReactiveUI; +using Splat; +using System.Reactive.Disposables; +using v2rayN.Desktop.Common; + +namespace v2rayN.Desktop.Views +{ + public partial class ProfilesView : ReactiveUserControl + { + private static Config _config; + private Window _window; + + public ProfilesView(Window window) + { + InitializeComponent(); + + _config = LazyConfig.Instance.Config; + _window = window; + + menuSelectAll.Click += menuSelectAll_Click; + btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; + txtServerFilter.KeyDown += TxtServerFilter_KeyDown; + lstProfiles.KeyDown += LstProfiles_KeyDown; + lstProfiles.SelectionChanged += lstProfiles_SelectionChanged; + lstProfiles.DoubleTapped += LstProfiles_DoubleTapped; + lstProfiles.LoadingRow += LstProfiles_LoadingRow; + //if (_config.uiItem.enableDragDropSort) + //{ + // lstProfiles.AllowDrop = true; + // lstProfiles.PreviewMouseLeftButtonDown += LstProfiles_PreviewMouseLeftButtonDown; + // lstProfiles.MouseMove += LstProfiles_MouseMove; + // lstProfiles.DragEnter += LstProfiles_DragEnter; + // lstProfiles.Drop += LstProfiles_Drop; + //} + + ViewModel = new ProfilesViewModel(UpdateViewHandler); + Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ProfilesViewModel)); + + this.WhenActivated(disposables => + { + this.OneWayBind(ViewModel, vm => vm.ProfileItems, v => v.lstProfiles.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedProfile, v => v.lstProfiles.SelectedItem).DisposeWith(disposables); + + // this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.lstGroup.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.btnAddSub).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.btnEditSub).DisposeWith(disposables); + + //servers delete + this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.menuEditServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.RemoveServerCmd, v => v.menuRemoveServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.RemoveDuplicateServerCmd, v => v.menuRemoveDuplicateServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SetDefaultMultipleServerCmd, v => v.menuSetDefaultMultipleServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SetDefaultLoadBalanceServerCmd, v => v.menuSetDefaultLoadBalanceServer).DisposeWith(disposables); + + //servers move + this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables); + + this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables); + + //servers ping + this.BindCommand(ViewModel, vm => vm.MixedTestServerCmd, v => v.menuMixedTestServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.TcpingServerCmd, v => v.menuTcpingServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.RealPingServerCmd, v => v.menuRealPingServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables); + + //servers export + this.BindCommand(ViewModel, vm => vm.Export2ClientConfigCmd, v => v.menuExport2ClientConfig).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables); + }); + + //RestoreUI(); + ViewModel?.RefreshServers(); + } + + //#region Event + + private async Task UpdateViewHandler(EViewAction action, object? obj) + { + switch (action) + { + case EViewAction.SetClipboardData: + if (obj is null) return false; + await AvaUtils.SetClipboardData(this, (string)obj); + break; + + case EViewAction.AdjustMainLvColWidth: + Dispatcher.UIThread.Post(() => + AutofitColumnWidth(), + DispatcherPriority.Default); + + break; + + case EViewAction.ProfilesFocus: + lstProfiles.Focus(); + break; + + case EViewAction.ShowYesNo: + if (await UI.ShowYesNo(_window, ResUI.RemoveServer) == ButtonResult.No) + { + return false; + } + break; + + case EViewAction.SaveFileDialog: + if (obj is null) return false; + var fileName = await UI.SaveFileDialog(_window, ""); + if (fileName.IsNullOrEmpty()) + { + return false; + } + ViewModel?.Export2ClientConfigResult(fileName, (ProfileItem)obj); + break; + + case EViewAction.AddServerWindow: + if (obj is null) return false; + return await new AddServerWindow((ProfileItem)obj).ShowDialog(_window); + + case EViewAction.AddServer2Window: + if (obj is null) return false; + return await new AddServer2Window((ProfileItem)obj).ShowDialog(_window); + + case EViewAction.ShareServer: + if (obj is null) return false; + await ShareServer((string)obj); + break; + + case EViewAction.SubEditWindow: + if (obj is null) return false; + return await new SubEditWindow((SubItem)obj).ShowDialog(_window); + + case EViewAction.DispatcherSpeedTest: + if (obj is null) return false; + Dispatcher.UIThread.Post(() => + ViewModel?.SetSpeedTestResult((SpeedTestResult)obj), + DispatcherPriority.Default); + + break; + + case EViewAction.DispatcherRefreshServersBiz: + Dispatcher.UIThread.Post(() => + ViewModel?.RefreshServersBiz(), + DispatcherPriority.Default); + break; + } + + return await Task.FromResult(true); + } + + public async Task ShareServer(string url) + { + if (Utils.IsNullOrEmpty(url)) + { + return; + } + var dialog = new QrcodeView(url); + await dialog.ShowDialog(_window); + } + + private void lstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e) + { + List lst = []; + foreach (var item in lstProfiles.SelectedItems) + { + lst.Add((ProfileItemModel)item); + } + ViewModel.SelectedProfiles = lst; + } + + private void LstProfiles_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e) + { + if (_config.uiItem.doubleClick2Activate) + { + ViewModel?.SetDefaultServer(); + } + else + { + ViewModel?.EditServerAsync(EConfigType.Custom); + } + } + + private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e) + { + e.Row.Header = $" {e.Row.GetIndex() + 1}"; + } + + //private void LstProfiles_ColumnHeader_Click(object? sender, RoutedEventArgs e) + //{ + // var colHeader = sender as DataGridColumnHeader; + // if (colHeader == null || colHeader.TabIndex < 0 || colHeader.Column == null) + // { + // return; + // } + + // var colName = ((MyDGTextColumn)colHeader.Column).ExName; + // ViewModel?.SortServer(colName); + //} + + private void menuSelectAll_Click(object? sender, RoutedEventArgs e) + { + lstProfiles.SelectAll(); + } + + private void LstProfiles_KeyDown(object? sender, KeyEventArgs e) + { + if (e.KeyModifiers == KeyModifiers.Control) + { + switch (e.Key) + { + case Key.A: + menuSelectAll_Click(null, null); + break; + + case Key.C: + ViewModel?.Export2ShareUrlAsync(false); + break; + + case Key.D: + ViewModel?.EditServerAsync(EConfigType.Custom); + break; + + case Key.F: + ViewModel?.ShareServerAsync(); + break; + + case Key.O: + ViewModel?.ServerSpeedtest(ESpeedActionType.Tcping); + break; + + case Key.R: + ViewModel?.ServerSpeedtest(ESpeedActionType.Realping); + break; + + case Key.T: + ViewModel?.ServerSpeedtest(ESpeedActionType.Speedtest); + break; + + case Key.E: + ViewModel?.ServerSpeedtest(ESpeedActionType.Mixedtest); + break; + } + } + else + { + if (e.Key is Key.Enter or Key.Return) + { + ViewModel?.SetDefaultServer(); + } + else if (e.Key == Key.Delete) + { + ViewModel?.RemoveServerAsync(); + } + else if (e.Key == Key.T) + { + ViewModel?.MoveServer(EMove.Top); + } + else if (e.Key == Key.U) + { + ViewModel?.MoveServer(EMove.Up); + } + else if (e.Key == Key.D) + { + ViewModel?.MoveServer(EMove.Down); + } + else if (e.Key == Key.B) + { + ViewModel?.MoveServer(EMove.Bottom); + } + else if (e.Key == Key.Escape) + { + ViewModel?.ServerSpeedtestStop(); + } + } + } + + private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e) + { + AutofitColumnWidth(); + } + + private void AutofitColumnWidth() + { + foreach (var it in lstProfiles.Columns) + { + it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto); + } + } + + private void TxtServerFilter_KeyDown(object? sender, KeyEventArgs e) + { + if (e.Key is Key.Enter or Key.Return) + { + ViewModel?.RefreshServers(); + } + } + + //#endregion Event + + //#region UI + + //private void RestoreUI() + //{ + // var lvColumnItem = _config.uiItem.mainColumnItem.OrderBy(t => t.Index).ToList(); + // var displayIndex = 0; + // foreach (var item in lvColumnItem) + // { + // foreach (MyDGTextColumn item2 in lstProfiles.Columns) + // { + // if (item2.ExName == item.Name) + // { + // if (item.Width < 0) + // { + // item2.IsVisible = false; + // } + // else + // { + // item2.Width = item.Width; + // item2.DisplayIndex = displayIndex++; + // } + // } + // } + // } + + // if (!_config.guiItem.enableStatistics) + // { + // colTodayUp.IsVisible = + // colTodayDown.IsVisible = + // colTotalUp.IsVisible = + // colTotalDown.IsVisible = false; + // } + // else + // { + // colTodayUp.IsVisible = + // colTodayDown.IsVisible = + // colTotalUp.IsVisible = + // colTotalDown.IsVisible = true; + // } + //} + + //private void StorageUI() + //{ + // List lvColumnItem = new(); + // for (int k = 0; k < lstProfiles.Columns.Count; k++) + // { + // var item2 = (MyDGTextColumn)lstProfiles.Columns[k]; + // lvColumnItem.Add(new() + // { + // Name = item2.ExName, + // Width = item2.IsVisible == true ? Utils.ToInt(item2.ActualWidth) : -1, + // Index = item2.DisplayIndex + // }); + // } + // _config.uiItem.mainColumnItem = lvColumnItem; + // ConfigHandler.SaveConfig(_config); + //} + + //#endregion UI + + //#region Drag and Drop + + //private Point startPoint = new(); + //private int startIndex = -1; + //private string formatData = "ProfileItemModel"; + + ///// + ///// Helper to search up the VisualTree + ///// + ///// + ///// + ///// + //private static T? FindAncestor(DependencyObject current) where T : DependencyObject + //{ + // do + // { + // if (current is T) + // { + // return (T)current; + // } + // current = VisualTreeHelper.GetParent(current); + // } + // while (current != null); + // return null; + //} + + //private void LstProfiles_PreviewMouseLeftButtonDown(object? sender, MouseButtonEventArgs e) + //{ + // // Get current mouse position + // startPoint = e.GetPosition(null); + //} + + //private void LstProfiles_MouseMove(object? sender, MouseEventArgs e) + //{ + // // Get the current mouse position + // Point mousePos = e.GetPosition(null); + // Vector diff = startPoint - mousePos; + + // if (e.LeftButton == MouseButtonState.Pressed && + // (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance || + // Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)) + // { + // // Get the dragged Item + // if (sender is not DataGrid listView) return; + // var listViewItem = FindAncestor((DependencyObject)e.OriginalSource); + // if (listViewItem == null) return; // Abort + // // Find the data behind the ListViewItem + // ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem); + // if (item == null) return; // Abort + // // Initialize the drag & drop operation + // startIndex = lstProfiles.SelectedIndex; + // DataObject dragData = new(formatData, item); + // DragDrop.DoDragDrop(listViewItem, dragData, DragDropEffects.Copy | DragDropEffects.Move); + // } + //} + + //private void LstProfiles_DragEnter(object? sender, DragEventArgs e) + //{ + // if (!e.Data.GetDataPresent(formatData) || sender != e.Source) + // { + // e.Effects = DragDropEffects.None; + // } + //} + + //private void LstProfiles_Drop(object? sender, DragEventArgs e) + //{ + // if (e.Data.GetDataPresent(formatData) && sender == e.Source) + // { + // // Get the drop Item destination + // if (sender is not DataGrid listView) return; + // var listViewItem = FindAncestor((DependencyObject)e.OriginalSource); + // if (listViewItem == null) + // { + // // Abort + // e.Effects = DragDropEffects.None; + // return; + // } + // // Find the data behind the Item + // ProfileItemModel item = (ProfileItemModel)listView.ItemContainerGenerator.ItemFromContainer(listViewItem); + // if (item == null) return; + // // Move item into observable collection + // // (this will be automatically reflected to lstView.ItemsSource) + // e.Effects = DragDropEffects.Move; + + // ViewModel?.MoveServerTo(startIndex, item); + + // startIndex = -1; + // } + //} + + //#endregion Drag and Drop + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml b/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml new file mode 100644 index 00000000..2f92209a --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/QrcodeView.axaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/Views/ThemeSettingView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/ThemeSettingView.axaml.cs new file mode 100644 index 00000000..1c544aad --- /dev/null +++ b/v2rayN/v2rayN.Desktop/Views/ThemeSettingView.axaml.cs @@ -0,0 +1,37 @@ +using Avalonia; +using Avalonia.ReactiveUI; +using ReactiveUI; +using System.Reactive.Disposables; +using v2rayN.Desktop.ViewModels; + +namespace v2rayN.Desktop.Views +{ + /// + /// ThemeSettingView.xaml + /// + public partial class ThemeSettingView : ReactiveUserControl + { + public ThemeSettingView() + { + InitializeComponent(); + ViewModel = new ThemeSettingViewModel(); + + for (int i = Global.MinFontSize; i <= Global.MinFontSize + 10; i++) + { + cmbCurrentFontSize.Items.Add(i); + } + + Global.Languages.ForEach(it => + { + cmbCurrentLanguage.Items.Add(it); + }); + + this.WhenActivated(disposables => + { + this.Bind(ViewModel, vm => vm.ColorModeDark, v => v.togDarkMode.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CurrentFontSize, v => v.cmbCurrentFontSize.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CurrentLanguage, v => v.cmbCurrentLanguage.SelectedValue).DisposeWith(disposables); + }); + } + } +} \ No newline at end of file diff --git a/v2rayN/v2rayN.Desktop/v2rayN.Desktop.csproj b/v2rayN/v2rayN.Desktop/v2rayN.Desktop.csproj new file mode 100644 index 00000000..b0fc246c --- /dev/null +++ b/v2rayN/v2rayN.Desktop/v2rayN.Desktop.csproj @@ -0,0 +1,39 @@ + + + WinExe + net8.0 + enable + enable + true + true + 6.55 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2rayN/v2rayN.sln b/v2rayN/v2rayN.sln index f4afd009..47689a40 100644 --- a/v2rayN/v2rayN.sln +++ b/v2rayN/v2rayN.sln @@ -9,9 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtosLib", "ProtosLib\Prot EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PacLib", "PacLib\PacLib.csproj", "{EE4E6CD8-8353-446B-8F29-A841A02AE5EC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayUpgrade", "v2rayUpgrade\v2rayUpgrade.csproj", "{3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceLib", "ServiceLib\ServiceLib.csproj", "{1B6456C4-FFAA-4298-80F6-7B689A6E9243}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLib", "ServiceLib\ServiceLib.csproj", "{1B6456C4-FFAA-4298-80F6-7B689A6E9243}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayN.Desktop", "v2rayN.Desktop\v2rayN.Desktop.csproj", "{5D16541A-F971-4C17-9315-BB8955E3F984}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "v2rayUpgrade", "v2rayUpgrade\v2rayUpgrade.csproj", "{47D68B1C-601C-4C69-873B-FFF0DC13EC97}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,14 +33,18 @@ Global {EE4E6CD8-8353-446B-8F29-A841A02AE5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE4E6CD8-8353-446B-8F29-A841A02AE5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE4E6CD8-8353-446B-8F29-A841A02AE5EC}.Release|Any CPU.Build.0 = Release|Any CPU - {3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CD0B9E8-331B-42C6-A395-4DA0FD4BC8EB}.Release|Any CPU.Build.0 = Release|Any CPU {1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B6456C4-FFAA-4298-80F6-7B689A6E9243}.Release|Any CPU.Build.0 = Release|Any CPU + {5D16541A-F971-4C17-9315-BB8955E3F984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D16541A-F971-4C17-9315-BB8955E3F984}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D16541A-F971-4C17-9315-BB8955E3F984}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D16541A-F971-4C17-9315-BB8955E3F984}.Release|Any CPU.Build.0 = Release|Any CPU + {47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47D68B1C-601C-4C69-873B-FFF0DC13EC97}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE