mirror of
				https://github.com/2dust/v2rayN.git
				synced 2025-10-26 10:14:42 +00:00 
			
		
		
		
	Compare commits
	
		
			1 commit
		
	
	
		
			3223b0bb13
			...
			727b924c41
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 727b924c41 | 
					 28 changed files with 149 additions and 344 deletions
				
			
		
							
								
								
									
										2
									
								
								.github/workflows/build-linux.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build-linux.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -22,7 +22,7 @@ jobs: | |||
|       matrix: | ||||
|         configuration: [Release] | ||||
| 
 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     runs-on: ubuntu-22.04 | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ | |||
|     <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" /> | ||||
|     <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" /> | ||||
|     <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" /> | ||||
|     <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" /> | ||||
|  | @ -20,7 +19,6 @@ | |||
|     <PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" /> | ||||
|     <PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" /> | ||||
|     <PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" /> | ||||
|     <PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" /> | ||||
|     <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.10" /> | ||||
|     <PackageVersion Include="NLog" Version="6.0.4" /> | ||||
|     <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> | ||||
|  |  | |||
|  | @ -1,32 +0,0 @@ | |||
| using System.Reactive; | ||||
| 
 | ||||
| namespace ServiceLib.Events; | ||||
| 
 | ||||
| public static class AppEvents | ||||
| { | ||||
|     public static readonly EventChannel<Unit> ReloadRequested = new(); | ||||
|     public static readonly EventChannel<bool?> ShowHideWindowRequested = new(); | ||||
|     public static readonly EventChannel<Unit> AddServerViaScanRequested = new(); | ||||
|     public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new(); | ||||
|     public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new(); | ||||
| 
 | ||||
|     public static readonly EventChannel<Unit> ProfilesRefreshRequested = new(); | ||||
|     public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new(); | ||||
|     public static readonly EventChannel<Unit> ProxiesReloadRequested = new(); | ||||
|     public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new(); | ||||
| 
 | ||||
|     public static readonly EventChannel<string> SendSnackMsgRequested = new(); | ||||
|     public static readonly EventChannel<string> SendMsgViewRequested = new(); | ||||
| 
 | ||||
|     public static readonly EventChannel<Unit> AppExitRequested = new(); | ||||
|     public static readonly EventChannel<bool> ShutdownRequested = new(); | ||||
| 
 | ||||
|     public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new(); | ||||
| 
 | ||||
|     public static readonly EventChannel<string> SetDefaultServerRequested = new(); | ||||
| 
 | ||||
|     public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new(); | ||||
|     public static readonly EventChannel<Unit> TestServerRequested = new(); | ||||
|     public static readonly EventChannel<Unit> InboundDisplayRequested = new(); | ||||
|     public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new(); | ||||
| } | ||||
|  | @ -1,29 +0,0 @@ | |||
| using System.Reactive; | ||||
| using System.Reactive.Linq; | ||||
| using System.Reactive.Subjects; | ||||
| 
 | ||||
| namespace ServiceLib.Events; | ||||
| 
 | ||||
| public sealed class EventChannel<T> | ||||
| { | ||||
|     private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>()); | ||||
| 
 | ||||
|     public IObservable<T> AsObservable() | ||||
|     { | ||||
|         return _subject.AsObservable(); | ||||
|     } | ||||
| 
 | ||||
|     public void Publish(T value) | ||||
|     { | ||||
|         _subject.OnNext(value); | ||||
|     } | ||||
| 
 | ||||
|     public void Publish() | ||||
|     { | ||||
|         if (typeof(T) != typeof(Unit)) | ||||
|         { | ||||
|             throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>."); | ||||
|         } | ||||
|         _subject.OnNext((T)(object)Unit.Default); | ||||
|     } | ||||
| } | ||||
|  | @ -450,14 +450,6 @@ public class Global | |||
|             "none" | ||||
|     ]; | ||||
| 
 | ||||
|     public static readonly Dictionary<string, string> LogLevelColors = new() | ||||
|     { | ||||
|         { "debug",   "#6C757D" }, | ||||
|         { "info",    "#2ECC71" }, | ||||
|         { "warning", "#FFA500" }, | ||||
|         { "error",   "#E74C3C" }, | ||||
|     }; | ||||
| 
 | ||||
|     public static readonly List<string> InboundTags = | ||||
|     [ | ||||
|         "socks", | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| global using ServiceLib.Base; | ||||
| global using ServiceLib.Common; | ||||
| global using ServiceLib.Enums; | ||||
| global using ServiceLib.Events; | ||||
| global using ServiceLib.Handler; | ||||
| global using ServiceLib.Helper; | ||||
| global using ServiceLib.Manager; | ||||
|  |  | |||
							
								
								
									
										33
									
								
								v2rayN/ServiceLib/Handler/AppEvents.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								v2rayN/ServiceLib/Handler/AppEvents.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| using System.Reactive; | ||||
| using System.Reactive.Subjects; | ||||
| 
 | ||||
| namespace ServiceLib.Handler; | ||||
| 
 | ||||
| public static class AppEvents | ||||
| { | ||||
|     public static readonly Subject<Unit> ReloadRequested = new(); | ||||
|     public static readonly Subject<bool?> ShowHideWindowRequested = new(); | ||||
|     public static readonly Subject<Unit> AddServerViaScanRequested = new(); | ||||
|     public static readonly Subject<Unit> AddServerViaClipboardRequested = new(); | ||||
|     public static readonly Subject<bool> SubscriptionsUpdateRequested = new(); | ||||
| 
 | ||||
|     public static readonly Subject<Unit> ProfilesRefreshRequested = new(); | ||||
|     public static readonly Subject<Unit> SubscriptionsRefreshRequested = new(); | ||||
|     public static readonly Subject<Unit> ProxiesReloadRequested = new(); | ||||
|     public static readonly Subject<ServerSpeedItem> DispatcherStatisticsRequested = new(); | ||||
| 
 | ||||
|     public static readonly Subject<string> SendSnackMsgRequested = new(); | ||||
|     public static readonly Subject<string> SendMsgViewRequested = new(); | ||||
| 
 | ||||
|     public static readonly Subject<Unit> AppExitRequested = new(); | ||||
|     public static readonly Subject<bool> ShutdownRequested = new(); | ||||
| 
 | ||||
|     public static readonly Subject<Unit> AdjustMainLvColWidthRequested = new(); | ||||
| 
 | ||||
|     public static readonly Subject<string> SetDefaultServerRequested = new(); | ||||
| 
 | ||||
|     public static readonly Subject<Unit> RoutingsMenuRefreshRequested = new(); | ||||
|     public static readonly Subject<Unit> TestServerRequested = new(); | ||||
|     public static readonly Subject<Unit> InboundDisplayRequested = new(); | ||||
|     public static readonly Subject<ESysProxyType> SysProxyChangeRequested = new(); | ||||
| } | ||||
|  | @ -97,7 +97,7 @@ public sealed class AppManager | |||
|             Logging.SaveLog("AppExitAsync Begin"); | ||||
| 
 | ||||
|             await SysProxyHandler.UpdateSysProxy(_config, true); | ||||
|             AppEvents.AppExitRequested.Publish(); | ||||
|             AppEvents.AppExitRequested.OnNext(Unit.Default); | ||||
|             await Task.Delay(50); //Wait for AppExitRequested to be processed | ||||
| 
 | ||||
|             await ConfigHandler.SaveConfig(_config); | ||||
|  | @ -121,7 +121,7 @@ public sealed class AppManager | |||
| 
 | ||||
|     public void Shutdown(bool byUser) | ||||
|     { | ||||
|         AppEvents.ShutdownRequested.Publish(byUser); | ||||
|         AppEvents.ShutdownRequested.OnNext(byUser); | ||||
|     } | ||||
| 
 | ||||
|     public async Task RebootAsAdmin() | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ public class NoticeManager | |||
|         { | ||||
|             return; | ||||
|         } | ||||
|         AppEvents.SendSnackMsgRequested.Publish(content); | ||||
|         AppEvents.SendSnackMsgRequested.OnNext(content); | ||||
|     } | ||||
| 
 | ||||
|     public void SendMessage(string? content) | ||||
|  | @ -20,7 +20,7 @@ public class NoticeManager | |||
|         { | ||||
|             return; | ||||
|         } | ||||
|         AppEvents.SendMsgViewRequested.Publish(content); | ||||
|         AppEvents.SendMsgViewRequested.OnNext(content); | ||||
|     } | ||||
| 
 | ||||
|     public void SendMessageEx(string? content) | ||||
|  |  | |||
|  | @ -223,7 +223,7 @@ public class CheckUpdateViewModel : MyReactiveObject | |||
|     { | ||||
|         if (blReload) | ||||
|         { | ||||
|             AppEvents.ReloadRequested.Publish(); | ||||
|             AppEvents.ReloadRequested.OnNext(Unit.Default); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|  |  | |||
|  | @ -303,7 +303,7 @@ public class MainWindowViewModel : MyReactiveObject | |||
|             } | ||||
|             if (_config.UiItem.EnableAutoAdjustMainLvColWidth) | ||||
|             { | ||||
|                 AppEvents.AdjustMainLvColWidthRequested.Publish(); | ||||
|                 AppEvents.AdjustMainLvColWidthRequested.OnNext(Unit.Default); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -314,7 +314,7 @@ public class MainWindowViewModel : MyReactiveObject | |||
|         { | ||||
|             return; | ||||
|         } | ||||
|         AppEvents.DispatcherStatisticsRequested.Publish(update); | ||||
|         AppEvents.DispatcherStatisticsRequested.OnNext(update); | ||||
|     } | ||||
| 
 | ||||
|     #endregion Actions | ||||
|  | @ -323,14 +323,14 @@ public class MainWindowViewModel : MyReactiveObject | |||
| 
 | ||||
|     private async Task RefreshServers() | ||||
|     { | ||||
|         AppEvents.ProfilesRefreshRequested.Publish(); | ||||
|         AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default); | ||||
| 
 | ||||
|         await Task.Delay(200); | ||||
|     } | ||||
| 
 | ||||
|     private void RefreshSubscriptions() | ||||
|     { | ||||
|         AppEvents.SubscriptionsRefreshRequested.Publish(); | ||||
|         AppEvents.SubscriptionsRefreshRequested.OnNext(Unit.Default); | ||||
|     } | ||||
| 
 | ||||
|     #endregion Servers && Groups | ||||
|  | @ -466,7 +466,7 @@ public class MainWindowViewModel : MyReactiveObject | |||
|         var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); | ||||
|         if (ret == true) | ||||
|         { | ||||
|             AppEvents.InboundDisplayRequested.Publish(); | ||||
|             AppEvents.InboundDisplayRequested.OnNext(Unit.Default); | ||||
|             await Reload(); | ||||
|         } | ||||
|     } | ||||
|  | @ -477,7 +477,7 @@ public class MainWindowViewModel : MyReactiveObject | |||
|         if (ret == true) | ||||
|         { | ||||
|             await ConfigHandler.InitBuiltinRouting(_config); | ||||
|             AppEvents.RoutingsMenuRefreshRequested.Publish(); | ||||
|             AppEvents.RoutingsMenuRefreshRequested.OnNext(Unit.Default); | ||||
|             await Reload(); | ||||
|         } | ||||
|     } | ||||
|  | @ -545,12 +545,12 @@ public class MainWindowViewModel : MyReactiveObject | |||
|             await SysProxyHandler.UpdateSysProxy(_config, false); | ||||
|             await Task.Delay(1000); | ||||
|         }); | ||||
|         AppEvents.TestServerRequested.Publish(); | ||||
|         AppEvents.TestServerRequested.OnNext(Unit.Default); | ||||
| 
 | ||||
|         var showClashUI = _config.IsRunningCore(ECoreType.sing_box); | ||||
|         if (showClashUI) | ||||
|         { | ||||
|             AppEvents.ProxiesReloadRequested.Publish(); | ||||
|             AppEvents.ProxiesReloadRequested.OnNext(Unit.Default); | ||||
|         } | ||||
| 
 | ||||
|         RxApp.MainThreadScheduler.Schedule(() => ReloadResult(showClashUI)); | ||||
|  | @ -580,7 +580,7 @@ public class MainWindowViewModel : MyReactiveObject | |||
|     { | ||||
|         if (_config.UiItem.AutoHideStartup) | ||||
|         { | ||||
|             AppEvents.ShowHideWindowRequested.Publish(false); | ||||
|             AppEvents.ShowHideWindowRequested.OnNext(false); | ||||
|         } | ||||
|         await Task.CompletedTask; | ||||
|     } | ||||
|  | @ -593,7 +593,7 @@ public class MainWindowViewModel : MyReactiveObject | |||
|     { | ||||
|         await ConfigHandler.ApplyRegionalPreset(_config, type); | ||||
|         await ConfigHandler.InitRouting(_config); | ||||
|         AppEvents.RoutingsMenuRefreshRequested.Publish(); | ||||
|         AppEvents.RoutingsMenuRefreshRequested.OnNext(Unit.Default); | ||||
| 
 | ||||
|         await ConfigHandler.SaveConfig(_config); | ||||
|         await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler); | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| using System.Collections.Concurrent; | ||||
| using System.Reactive.Linq; | ||||
| using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
| using ReactiveUI; | ||||
| using ReactiveUI.Fody.Helpers; | ||||
|  | @ -10,9 +9,9 @@ namespace ServiceLib.ViewModels; | |||
| public class MsgViewModel : MyReactiveObject | ||||
| { | ||||
|     private readonly ConcurrentQueue<string> _queueMsg = new(); | ||||
|     private volatile bool _lastMsgFilterNotAvailable; | ||||
|     private int _showLock = 0; // 0 = unlocked, 1 = locked | ||||
|     public int NumMaxMsg { get; } = 500; | ||||
|     private readonly int _numMaxMsg = 500; | ||||
|     private bool _lastMsgFilterNotAvailable; | ||||
|     private bool _blLockShow = false; | ||||
| 
 | ||||
|     [Reactive] | ||||
|     public string MsgFilter { get; set; } | ||||
|  | @ -34,52 +33,46 @@ public class MsgViewModel : MyReactiveObject | |||
|         this.WhenAnyValue( | ||||
|           x => x.AutoRefresh, | ||||
|           y => y == true) | ||||
|               .Subscribe(c => _config.MsgUIItem.AutoRefresh = AutoRefresh); | ||||
|               .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); | ||||
| 
 | ||||
|         AppEvents.SendMsgViewRequested | ||||
|          .AsObservable() | ||||
|          //.ObserveOn(RxApp.MainThreadScheduler) | ||||
|          .Subscribe(content => _ = AppendQueueMsg(content)); | ||||
|          .Subscribe(async content => await AppendQueueMsg(content)); | ||||
|     } | ||||
| 
 | ||||
|     private async Task AppendQueueMsg(string msg) | ||||
|     { | ||||
|         //if (msg == Global.CommandClearMsg) | ||||
|         //{ | ||||
|         //    ClearMsg(); | ||||
|         //    return; | ||||
|         //} | ||||
|         if (AutoRefresh == false) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|         _ = EnqueueQueueMsg(msg); | ||||
| 
 | ||||
|         EnqueueQueueMsg(msg); | ||||
| 
 | ||||
|         if (_blLockShow) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|         if (!_config.UiItem.ShowInTaskbar) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (Interlocked.CompareExchange(ref _showLock, 1, 0) != 0) | ||||
|         { | ||||
|             return; | ||||
|         _blLockShow = true; | ||||
| 
 | ||||
|         await Task.Delay(500); | ||||
|         var txt = string.Join("", _queueMsg.ToArray()); | ||||
|         await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt); | ||||
| 
 | ||||
|         _blLockShow = false; | ||||
|     } | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             await Task.Delay(500).ConfigureAwait(false); | ||||
| 
 | ||||
|             var sb = new StringBuilder(); | ||||
|             while (_queueMsg.TryDequeue(out var line)) | ||||
|             { | ||||
|                 sb.Append(line); | ||||
|             } | ||||
| 
 | ||||
|             await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString()); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             Interlocked.Exchange(ref _showLock, 0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void EnqueueQueueMsg(string msg) | ||||
|     private async Task EnqueueQueueMsg(string msg) | ||||
|     { | ||||
|         //filter msg | ||||
|         if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) | ||||
|  | @ -98,17 +91,26 @@ public class MsgViewModel : MyReactiveObject | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         //Enqueue | ||||
|         if (_queueMsg.Count > _numMaxMsg) | ||||
|         { | ||||
|             for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) | ||||
|             { | ||||
|                 _queueMsg.TryDequeue(out _); | ||||
|             } | ||||
|         } | ||||
|         _queueMsg.Enqueue(msg); | ||||
|         if (!msg.EndsWith(Environment.NewLine)) | ||||
|         { | ||||
|             _queueMsg.Enqueue(Environment.NewLine); | ||||
|         } | ||||
|         await Task.CompletedTask; | ||||
|     } | ||||
| 
 | ||||
|     //public void ClearMsg() | ||||
|     //{ | ||||
|     //    _queueMsg.Clear(); | ||||
|     //} | ||||
|     public void ClearMsg() | ||||
|     { | ||||
|         _queueMsg.Clear(); | ||||
|     } | ||||
| 
 | ||||
|     private void DoMsgFilter() | ||||
|     { | ||||
|  |  | |||
|  | @ -285,7 +285,7 @@ public class ProfilesViewModel : MyReactiveObject | |||
| 
 | ||||
|     private void Reload() | ||||
|     { | ||||
|         AppEvents.ReloadRequested.Publish(); | ||||
|         AppEvents.ReloadRequested.OnNext(Unit.Default); | ||||
|     } | ||||
| 
 | ||||
|     public async Task SetSpeedTestResult(SpeedTestResult result) | ||||
|  | @ -371,7 +371,7 @@ public class ProfilesViewModel : MyReactiveObject | |||
| 
 | ||||
|     public async Task RefreshServers() | ||||
|     { | ||||
|         AppEvents.ProfilesRefreshRequested.Publish(); | ||||
|         AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default); | ||||
| 
 | ||||
|         await Task.Delay(200); | ||||
|     } | ||||
|  |  | |||
|  | @ -148,17 +148,17 @@ public class StatusBarViewModel : MyReactiveObject | |||
| 
 | ||||
|         NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () => | ||||
|         { | ||||
|             AppEvents.ShowHideWindowRequested.Publish(null); | ||||
|             AppEvents.ShowHideWindowRequested.OnNext(null); | ||||
|             await Task.CompletedTask; | ||||
|         }); | ||||
|         ShowWindowCmd = ReactiveCommand.CreateFromTask(async () => | ||||
|         { | ||||
|             AppEvents.ShowHideWindowRequested.Publish(true); | ||||
|             AppEvents.ShowHideWindowRequested.OnNext(true); | ||||
|             await Task.CompletedTask; | ||||
|         }); | ||||
|         HideWindowCmd = ReactiveCommand.CreateFromTask(async () => | ||||
|         { | ||||
|             AppEvents.ShowHideWindowRequested.Publish(false); | ||||
|             AppEvents.ShowHideWindowRequested.OnNext(false); | ||||
|             await Task.CompletedTask; | ||||
|         }); | ||||
| 
 | ||||
|  | @ -274,19 +274,19 @@ public class StatusBarViewModel : MyReactiveObject | |||
| 
 | ||||
|     private async Task AddServerViaClipboard() | ||||
|     { | ||||
|         AppEvents.AddServerViaClipboardRequested.Publish(); | ||||
|         AppEvents.AddServerViaClipboardRequested.OnNext(Unit.Default); | ||||
|         await Task.Delay(1000); | ||||
|     } | ||||
| 
 | ||||
|     private async Task AddServerViaScan() | ||||
|     { | ||||
|         AppEvents.AddServerViaScanRequested.Publish(); | ||||
|         AppEvents.AddServerViaScanRequested.OnNext(Unit.Default); | ||||
|         await Task.Delay(1000); | ||||
|     } | ||||
| 
 | ||||
|     private async Task UpdateSubscriptionProcess(bool blProxy) | ||||
|     { | ||||
|         AppEvents.SubscriptionsUpdateRequested.Publish(blProxy); | ||||
|         AppEvents.SubscriptionsUpdateRequested.OnNext(blProxy); | ||||
|         await Task.Delay(1000); | ||||
|     } | ||||
| 
 | ||||
|  | @ -348,7 +348,7 @@ public class StatusBarViewModel : MyReactiveObject | |||
|         { | ||||
|             return; | ||||
|         } | ||||
|         AppEvents.SetDefaultServerRequested.Publish(SelectedServer.ID); | ||||
|         AppEvents.SetDefaultServerRequested.OnNext(SelectedServer.ID); | ||||
|     } | ||||
| 
 | ||||
|     public async Task TestServerAvailability() | ||||
|  | @ -449,7 +449,7 @@ public class StatusBarViewModel : MyReactiveObject | |||
|         if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) | ||||
|         { | ||||
|             NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting); | ||||
|             AppEvents.ReloadRequested.Publish(); | ||||
|             AppEvents.ReloadRequested.OnNext(Unit.Default); | ||||
|             _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); | ||||
|         } | ||||
|     } | ||||
|  | @ -496,7 +496,7 @@ public class StatusBarViewModel : MyReactiveObject | |||
|             } | ||||
|         } | ||||
|         await ConfigHandler.SaveConfig(_config); | ||||
|         AppEvents.ReloadRequested.Publish(); | ||||
|         AppEvents.ReloadRequested.OnNext(Unit.Default); | ||||
|     } | ||||
| 
 | ||||
|     private bool AllowEnableTun() | ||||
|  |  | |||
|  | @ -11,7 +11,6 @@ | |||
|     RequestedThemeVariant="Default"> | ||||
|     <Application.Styles> | ||||
|         <semi:SemiTheme /> | ||||
|         <semi:AvaloniaEditSemiTheme /> | ||||
|         <StyleInclude Source="Assets/GlobalStyles.axaml" /> | ||||
|         <StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" /> | ||||
|         <dialogHost:DialogHostStyles /> | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ public partial class App : Application | |||
|         { | ||||
|             if (desktop.MainWindow != null) | ||||
|             { | ||||
|                 AppEvents.AddServerViaClipboardRequested.Publish(); | ||||
|                 AppEvents.AddServerViaClipboardRequested.OnNext(Unit.Default); | ||||
|                 await Task.Delay(1000); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -1,129 +0,0 @@ | |||
| using Avalonia.Media; | ||||
| using AvaloniaEdit; | ||||
| using AvaloniaEdit.Document; | ||||
| using AvaloniaEdit.Rendering; | ||||
| 
 | ||||
| namespace v2rayN.Desktop.Common; | ||||
| 
 | ||||
| public class KeywordColorizer : DocumentColorizingTransformer | ||||
| { | ||||
|     private readonly string[] _keywords; | ||||
|     private readonly Dictionary<string, IBrush> _brushMap; | ||||
| 
 | ||||
|     public KeywordColorizer(IDictionary<string, IBrush> keywordBrushMap) | ||||
|     { | ||||
|         if (keywordBrushMap == null || keywordBrushMap.Count == 0) | ||||
|         { | ||||
|             throw new ArgumentException("keywordBrushMap must not be null or empty", nameof(keywordBrushMap)); | ||||
|         } | ||||
| 
 | ||||
|         _brushMap = new Dictionary<string, IBrush>(StringComparer.OrdinalIgnoreCase); | ||||
|         foreach (var kvp in keywordBrushMap) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(kvp.Key) || kvp.Value == null) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (!_brushMap.ContainsKey(kvp.Key)) | ||||
|             { | ||||
|                 _brushMap[kvp.Key] = kvp.Value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (_brushMap.Count == 0) | ||||
|         { | ||||
|             throw new ArgumentException("keywordBrushMap must contain at least one non-empty key with a non-null brush", nameof(keywordBrushMap)); | ||||
|         } | ||||
| 
 | ||||
|         _keywords = _brushMap.Keys.ToArray(); | ||||
|     } | ||||
| 
 | ||||
|     protected override void ColorizeLine(DocumentLine line) | ||||
|     { | ||||
|         var text = CurrentContext.Document.GetText(line); | ||||
|         if (string.IsNullOrEmpty(text)) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         foreach (var kw in _keywords) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(kw)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             var searchStart = 0; | ||||
|             while (true) | ||||
|             { | ||||
|                 var idx = text.IndexOf(kw, searchStart, StringComparison.OrdinalIgnoreCase); | ||||
|                 if (idx < 0) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 var kwEndIndex = idx + kw.Length; | ||||
|                 if (IsWordCharBefore(text, idx) || IsWordCharAfter(text, kwEndIndex)) | ||||
|                 { | ||||
|                     searchStart = idx + Math.Max(1, kw.Length); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 var start = line.Offset + idx; | ||||
|                 var end = start + kw.Length; | ||||
| 
 | ||||
|                 if (_brushMap.TryGetValue(kw, out var brush) && brush != null) | ||||
|                 { | ||||
|                     ChangeLinePart(start, end, element => element.TextRunProperties.SetForegroundBrush(brush)); | ||||
|                 } | ||||
| 
 | ||||
|                 searchStart = idx + Math.Max(1, kw.Length); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static bool IsWordCharBefore(string text, int idx) | ||||
|     { | ||||
|         if (idx <= 0) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         var c = text[idx - 1]; | ||||
|         return char.IsLetterOrDigit(c) || c == '_'; | ||||
|     } | ||||
| 
 | ||||
|     private static bool IsWordCharAfter(string text, int idx) | ||||
|     { | ||||
|         if (idx >= text.Length) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         var c = text[idx]; | ||||
|         return char.IsLetterOrDigit(c) || c == '_'; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| public static class TextEditorKeywordHighlighter | ||||
| { | ||||
|     public static void Attach(TextEditor editor, IDictionary<string, IBrush> keywordBrushMap) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(editor); | ||||
| 
 | ||||
|         if (keywordBrushMap == null || keywordBrushMap.Count == 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (editor.TextArea?.TextView?.LineTransformers?.OfType<KeywordColorizer>().Any() == true) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         var colorizer = new KeywordColorizer(keywordBrushMap); | ||||
|         editor.TextArea.TextView.LineTransformers.Add(colorizer); | ||||
|         editor.TextArea.TextView.InvalidateVisual(); | ||||
|     } | ||||
| } | ||||
|  | @ -2,7 +2,6 @@ global using ServiceLib; | |||
| global using ServiceLib.Base; | ||||
| global using ServiceLib.Common; | ||||
| global using ServiceLib.Enums; | ||||
| global using ServiceLib.Events; | ||||
| global using ServiceLib.Handler; | ||||
| global using ServiceLib.Manager; | ||||
| global using ServiceLib.Models; | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ using Avalonia.Controls.Notifications; | |||
| using Avalonia.Controls.Primitives; | ||||
| using Avalonia.Media; | ||||
| using Avalonia.Styling; | ||||
| using AvaloniaEdit; | ||||
| using ReactiveUI; | ||||
| using ReactiveUI.Fody.Helpers; | ||||
| using Semi.Avalonia; | ||||
|  | @ -113,8 +112,7 @@ public class ThemeSettingViewModel : MyReactiveObject | |||
|             x.OfType<ContextMenu>(), | ||||
|             x.OfType<DataGridRow>(), | ||||
|             x.OfType<ListBoxItem>(), | ||||
|             x.OfType<HeaderedContentControl>(), | ||||
|             x.OfType<TextEditor>() | ||||
|             x.OfType<HeaderedContentControl>() | ||||
|         )); | ||||
|         style.Add(new Setter() | ||||
|         { | ||||
|  | @ -155,8 +153,7 @@ public class ThemeSettingViewModel : MyReactiveObject | |||
|                 x.OfType<DataGridRow>(), | ||||
|                 x.OfType<ListBoxItem>(), | ||||
|                 x.OfType<HeaderedContentControl>(), | ||||
|                 x.OfType<WindowNotificationManager>(), | ||||
|                 x.OfType<TextEditor>() | ||||
|                 x.OfType<WindowNotificationManager>() | ||||
|             )); | ||||
|             style.Add(new Setter() | ||||
|             { | ||||
|  |  | |||
|  | @ -260,7 +260,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel> | |||
|             case EGlobalHotkey.SystemProxySet: | ||||
|             case EGlobalHotkey.SystemProxyUnchanged: | ||||
|             case EGlobalHotkey.SystemProxyPac: | ||||
|                 AppEvents.SysProxyChangeRequested.Publish((ESysProxyType)((int)e - 1)); | ||||
|                 AppEvents.SysProxyChangeRequested.OnNext((ESysProxyType)((int)e - 1)); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
|     x:Class="v2rayN.Desktop.Views.MsgView" | ||||
|     xmlns="https://github.com/avaloniaui" | ||||
|     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||||
|     xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit" | ||||
|     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||||
|     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||
|     xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" | ||||
|  | @ -71,17 +70,16 @@ | |||
|                 Theme="{DynamicResource SimpleToggleSwitch}" /> | ||||
|         </WrapPanel> | ||||
| 
 | ||||
|         <avaloniaEdit:TextEditor | ||||
|         <ScrollViewer x:Name="msgScrollViewer" VerticalScrollBarVisibility="Auto"> | ||||
|             <SelectableTextBlock | ||||
|                 Name="txtMsg" | ||||
|                 Margin="{StaticResource Margin8}" | ||||
|             IsReadOnly="True" | ||||
|             VerticalScrollBarVisibility="Auto" | ||||
|             WordWrap="True"> | ||||
|             <avaloniaEdit:TextEditor.Options> | ||||
|                 <avaloniaEdit:TextEditorOptions AllowScrollBelowDocument="False"/> | ||||
|             </avaloniaEdit:TextEditor.Options> | ||||
|             <avaloniaEdit:TextEditor.ContextFlyout> | ||||
|                 <MenuFlyout> | ||||
|                 VerticalAlignment="Stretch" | ||||
|                 Classes="TextArea" | ||||
|                 TextAlignment="Left" | ||||
|                 TextWrapping="Wrap"> | ||||
|                 <SelectableTextBlock.ContextMenu> | ||||
|                     <ContextMenu> | ||||
|                         <MenuItem | ||||
|                             x:Name="menuMsgViewSelectAll" | ||||
|                             Click="menuMsgViewSelectAll_Click" | ||||
|  | @ -98,9 +96,9 @@ | |||
|                             x:Name="menuMsgViewClear" | ||||
|                             Click="menuMsgViewClear_Click" | ||||
|                             Header="{x:Static resx:ResUI.menuMsgViewClear}" /> | ||||
|                 </MenuFlyout> | ||||
|             </avaloniaEdit:TextEditor.ContextFlyout> | ||||
|         </avaloniaEdit:TextEditor> | ||||
| 
 | ||||
|                     </ContextMenu> | ||||
|                 </SelectableTextBlock.ContextMenu> | ||||
|             </SelectableTextBlock> | ||||
|         </ScrollViewer> | ||||
|     </DockPanel> | ||||
| </UserControl> | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| using System.Reactive.Disposables; | ||||
| using Avalonia.Controls; | ||||
| using Avalonia.Interactivity; | ||||
| using Avalonia.Media; | ||||
| using Avalonia.ReactiveUI; | ||||
| using Avalonia.Threading; | ||||
| using ReactiveUI; | ||||
|  | @ -10,11 +10,13 @@ namespace v2rayN.Desktop.Views; | |||
| 
 | ||||
| public partial class MsgView : ReactiveUserControl<MsgViewModel> | ||||
| { | ||||
|     //private const int KeepLines = 30; | ||||
|     private readonly ScrollViewer _scrollViewer; | ||||
| 
 | ||||
|     public MsgView() | ||||
|     { | ||||
|         InitializeComponent(); | ||||
|         _scrollViewer = this.FindControl<ScrollViewer>("msgScrollViewer"); | ||||
| 
 | ||||
|         ViewModel = new MsgViewModel(UpdateViewHandler); | ||||
| 
 | ||||
|         this.WhenActivated(disposables => | ||||
|  | @ -22,11 +24,6 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel> | |||
|             this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables); | ||||
|             this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); | ||||
|         }); | ||||
| 
 | ||||
|         TextEditorKeywordHighlighter.Attach(txtMsg, Global.LogLevelColors.ToDictionary( | ||||
|                 kv => kv.Key, | ||||
|                 kv => (IBrush)new SolidColorBrush(Color.Parse(kv.Value)) | ||||
|             )); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<bool> UpdateViewHandler(EViewAction action, object? obj) | ||||
|  | @ -37,7 +34,8 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel> | |||
|                 if (obj is null) | ||||
|                     return false; | ||||
| 
 | ||||
|                 Dispatcher.UIThread.Post(() => ShowMsg(obj), | ||||
|                 Dispatcher.UIThread.Post(() => | ||||
|                     ShowMsg(obj), | ||||
|                    DispatcherPriority.ApplicationIdle); | ||||
|                 break; | ||||
|         } | ||||
|  | @ -46,37 +44,23 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel> | |||
| 
 | ||||
|     private void ShowMsg(object msg) | ||||
|     { | ||||
|         //var lineCount = txtMsg.LineCount; | ||||
|         //if (lineCount > ViewModel?.NumMaxMsg) | ||||
|         //{ | ||||
|         //    var cutLine = txtMsg.Document.GetLineByNumber(lineCount - KeepLines); | ||||
|         //    txtMsg.Document.Remove(0, cutLine.Offset); | ||||
|         //} | ||||
|         if (txtMsg.LineCount > ViewModel?.NumMaxMsg) | ||||
|         { | ||||
|             ClearMsg(); | ||||
|         } | ||||
| 
 | ||||
|         txtMsg.AppendText(msg.ToString()); | ||||
|         txtMsg.Text = msg.ToString(); | ||||
|         if (togScrollToEnd.IsChecked ?? true) | ||||
|         { | ||||
|             txtMsg.ScrollToEnd(); | ||||
|             _scrollViewer?.ScrollToEnd(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void ClearMsg() | ||||
|     { | ||||
|         txtMsg.Clear(); | ||||
|         txtMsg.AppendText("----- Message cleared -----\n"); | ||||
|         ViewModel?.ClearMsg(); | ||||
|         txtMsg.Text = ""; | ||||
|     } | ||||
| 
 | ||||
|     private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) | ||||
|     { | ||||
|         Dispatcher.UIThread.Post(() => | ||||
|         { | ||||
|             txtMsg.TextArea.Focus(); | ||||
|         txtMsg.Focus(); | ||||
|         txtMsg.SelectAll(); | ||||
|         }, DispatcherPriority.Render); | ||||
|     } | ||||
| 
 | ||||
|     private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e) | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ | |||
|     mc:Ignorable="d"> | ||||
| 
 | ||||
|     <UserControl.Resources> | ||||
|         <sys:Double x:Key="QrcodeWidth">400</sys:Double> | ||||
|         <sys:Double x:Key="QrcodeWidth">500</sys:Double> | ||||
|     </UserControl.Resources> | ||||
| 
 | ||||
|     <Grid Margin="32" RowDefinitions="Auto,Auto"> | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ | |||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Avalonia.AvaloniaEdit" /> | ||||
| 		<PackageReference Include="Avalonia.Controls.DataGrid"> | ||||
| 			<TreatAsUsed>true</TreatAsUsed> | ||||
| 		</PackageReference> | ||||
|  | @ -18,7 +17,6 @@ | |||
| 		<PackageReference Include="Avalonia.ReactiveUI" /> | ||||
| 		<PackageReference Include="MessageBox.Avalonia" /> | ||||
| 		<PackageReference Include="Semi.Avalonia" /> | ||||
| 		<PackageReference Include="Semi.Avalonia.AvaloniaEdit" /> | ||||
| 		<PackageReference Include="Semi.Avalonia.DataGrid"> | ||||
| 			<TreatAsUsed>true</TreatAsUsed> | ||||
| 		</PackageReference> | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ global using ServiceLib; | |||
| global using ServiceLib.Base; | ||||
| global using ServiceLib.Common; | ||||
| global using ServiceLib.Enums; | ||||
| global using ServiceLib.Events; | ||||
| global using ServiceLib.Handler; | ||||
| global using ServiceLib.Manager; | ||||
| global using ServiceLib.Models; | ||||
|  |  | |||
|  | @ -248,7 +248,7 @@ public partial class MainWindow | |||
|             case EGlobalHotkey.SystemProxySet: | ||||
|             case EGlobalHotkey.SystemProxyUnchanged: | ||||
|             case EGlobalHotkey.SystemProxyPac: | ||||
|                 AppEvents.SysProxyChangeRequested.Publish((ESysProxyType)((int)e - 1)); | ||||
|                 AppEvents.SysProxyChangeRequested.OnNext((ESysProxyType)((int)e - 1)); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -47,22 +47,19 @@ public partial class MsgView | |||
| 
 | ||||
|     private void ShowMsg(object msg) | ||||
|     { | ||||
|         if (txtMsg.LineCount > ViewModel?.NumMaxMsg) | ||||
|         { | ||||
|             ClearMsg(); | ||||
|         } | ||||
| 
 | ||||
|         txtMsg.AppendText(msg.ToString()); | ||||
|         txtMsg.BeginChange(); | ||||
|         txtMsg.Text = msg.ToString(); | ||||
|         if (togScrollToEnd.IsChecked ?? true) | ||||
|         { | ||||
|             txtMsg.ScrollToEnd(); | ||||
|         } | ||||
|         txtMsg.EndChange(); | ||||
|     } | ||||
| 
 | ||||
|     public void ClearMsg() | ||||
|     { | ||||
|         ViewModel?.ClearMsg(); | ||||
|         txtMsg.Clear(); | ||||
|         txtMsg.AppendText("----- Message cleared -----\n"); | ||||
|     } | ||||
| 
 | ||||
|     private void menuMsgViewSelectAll_Click(object sender, System.Windows.RoutedEventArgs e) | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
|     Style="{StaticResource ViewGlobal}" | ||||
|     mc:Ignorable="d"> | ||||
|     <UserControl.Resources> | ||||
|         <sys:Double x:Key="QrcodeWidth">400</sys:Double> | ||||
|         <sys:Double x:Key="QrcodeWidth">500</sys:Double> | ||||
|     </UserControl.Resources> | ||||
| 
 | ||||
|     <Grid Margin="32"> | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue