Compare commits

..

1 commit

Author SHA1 Message Date
DHR60
727b924c41
Merge 11924b545f into faff8e4ea2 2025-09-24 18:45:20 +08:00
28 changed files with 149 additions and 344 deletions

View file

@ -22,7 +22,7 @@ jobs:
matrix: matrix:
configuration: [Release] configuration: [Release]
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout

View file

@ -5,7 +5,6 @@
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled> <CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" 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.Fody" Version="19.5.41" />
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" /> <PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" /> <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="Semi.Avalonia.DataGrid" Version="11.2.1.10" />
<PackageVersion Include="NLog" Version="6.0.4" /> <PackageVersion Include="NLog" Version="6.0.4" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" /> <PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
@ -29,4 +27,4 @@
<PackageVersion Include="YamlDotNet" Version="16.3.0" /> <PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" /> <PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -450,14 +450,6 @@ public class Global
"none" "none"
]; ];
public static readonly Dictionary<string, string> LogLevelColors = new()
{
{ "debug", "#6C757D" },
{ "info", "#2ECC71" },
{ "warning", "#FFA500" },
{ "error", "#E74C3C" },
};
public static readonly List<string> InboundTags = public static readonly List<string> InboundTags =
[ [
"socks", "socks",

View file

@ -1,7 +1,6 @@
global using ServiceLib.Base; global using ServiceLib.Base;
global using ServiceLib.Common; global using ServiceLib.Common;
global using ServiceLib.Enums; global using ServiceLib.Enums;
global using ServiceLib.Events;
global using ServiceLib.Handler; global using ServiceLib.Handler;
global using ServiceLib.Helper; global using ServiceLib.Helper;
global using ServiceLib.Manager; global using ServiceLib.Manager;

View 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();
}

View file

@ -97,7 +97,7 @@ public sealed class AppManager
Logging.SaveLog("AppExitAsync Begin"); Logging.SaveLog("AppExitAsync Begin");
await SysProxyHandler.UpdateSysProxy(_config, true); await SysProxyHandler.UpdateSysProxy(_config, true);
AppEvents.AppExitRequested.Publish(); AppEvents.AppExitRequested.OnNext(Unit.Default);
await Task.Delay(50); //Wait for AppExitRequested to be processed await Task.Delay(50); //Wait for AppExitRequested to be processed
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
@ -121,7 +121,7 @@ public sealed class AppManager
public void Shutdown(bool byUser) public void Shutdown(bool byUser)
{ {
AppEvents.ShutdownRequested.Publish(byUser); AppEvents.ShutdownRequested.OnNext(byUser);
} }
public async Task RebootAsAdmin() public async Task RebootAsAdmin()

View file

@ -11,7 +11,7 @@ public class NoticeManager
{ {
return; return;
} }
AppEvents.SendSnackMsgRequested.Publish(content); AppEvents.SendSnackMsgRequested.OnNext(content);
} }
public void SendMessage(string? content) public void SendMessage(string? content)
@ -20,7 +20,7 @@ public class NoticeManager
{ {
return; return;
} }
AppEvents.SendMsgViewRequested.Publish(content); AppEvents.SendMsgViewRequested.OnNext(content);
} }
public void SendMessageEx(string? content) public void SendMessageEx(string? content)

View file

@ -223,7 +223,7 @@ public class CheckUpdateViewModel : MyReactiveObject
{ {
if (blReload) if (blReload)
{ {
AppEvents.ReloadRequested.Publish(); AppEvents.ReloadRequested.OnNext(Unit.Default);
} }
else else
{ {

View file

@ -303,7 +303,7 @@ public class MainWindowViewModel : MyReactiveObject
} }
if (_config.UiItem.EnableAutoAdjustMainLvColWidth) if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
{ {
AppEvents.AdjustMainLvColWidthRequested.Publish(); AppEvents.AdjustMainLvColWidthRequested.OnNext(Unit.Default);
} }
} }
} }
@ -314,7 +314,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
return; return;
} }
AppEvents.DispatcherStatisticsRequested.Publish(update); AppEvents.DispatcherStatisticsRequested.OnNext(update);
} }
#endregion Actions #endregion Actions
@ -323,14 +323,14 @@ public class MainWindowViewModel : MyReactiveObject
private async Task RefreshServers() private async Task RefreshServers()
{ {
AppEvents.ProfilesRefreshRequested.Publish(); AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
await Task.Delay(200); await Task.Delay(200);
} }
private void RefreshSubscriptions() private void RefreshSubscriptions()
{ {
AppEvents.SubscriptionsRefreshRequested.Publish(); AppEvents.SubscriptionsRefreshRequested.OnNext(Unit.Default);
} }
#endregion Servers && Groups #endregion Servers && Groups
@ -466,7 +466,7 @@ public class MainWindowViewModel : MyReactiveObject
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null); var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
if (ret == true) if (ret == true)
{ {
AppEvents.InboundDisplayRequested.Publish(); AppEvents.InboundDisplayRequested.OnNext(Unit.Default);
await Reload(); await Reload();
} }
} }
@ -477,7 +477,7 @@ public class MainWindowViewModel : MyReactiveObject
if (ret == true) if (ret == true)
{ {
await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinRouting(_config);
AppEvents.RoutingsMenuRefreshRequested.Publish(); AppEvents.RoutingsMenuRefreshRequested.OnNext(Unit.Default);
await Reload(); await Reload();
} }
} }
@ -545,12 +545,12 @@ public class MainWindowViewModel : MyReactiveObject
await SysProxyHandler.UpdateSysProxy(_config, false); await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000); await Task.Delay(1000);
}); });
AppEvents.TestServerRequested.Publish(); AppEvents.TestServerRequested.OnNext(Unit.Default);
var showClashUI = _config.IsRunningCore(ECoreType.sing_box); var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
if (showClashUI) if (showClashUI)
{ {
AppEvents.ProxiesReloadRequested.Publish(); AppEvents.ProxiesReloadRequested.OnNext(Unit.Default);
} }
RxApp.MainThreadScheduler.Schedule(() => ReloadResult(showClashUI)); RxApp.MainThreadScheduler.Schedule(() => ReloadResult(showClashUI));
@ -580,7 +580,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
if (_config.UiItem.AutoHideStartup) if (_config.UiItem.AutoHideStartup)
{ {
AppEvents.ShowHideWindowRequested.Publish(false); AppEvents.ShowHideWindowRequested.OnNext(false);
} }
await Task.CompletedTask; await Task.CompletedTask;
} }
@ -593,7 +593,7 @@ public class MainWindowViewModel : MyReactiveObject
{ {
await ConfigHandler.ApplyRegionalPreset(_config, type); await ConfigHandler.ApplyRegionalPreset(_config, type);
await ConfigHandler.InitRouting(_config); await ConfigHandler.InitRouting(_config);
AppEvents.RoutingsMenuRefreshRequested.Publish(); AppEvents.RoutingsMenuRefreshRequested.OnNext(Unit.Default);
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler); await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);

View file

@ -1,6 +1,5 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
@ -10,9 +9,9 @@ namespace ServiceLib.ViewModels;
public class MsgViewModel : MyReactiveObject public class MsgViewModel : MyReactiveObject
{ {
private readonly ConcurrentQueue<string> _queueMsg = new(); private readonly ConcurrentQueue<string> _queueMsg = new();
private volatile bool _lastMsgFilterNotAvailable; private readonly int _numMaxMsg = 500;
private int _showLock = 0; // 0 = unlocked, 1 = locked private bool _lastMsgFilterNotAvailable;
public int NumMaxMsg { get; } = 500; private bool _blLockShow = false;
[Reactive] [Reactive]
public string MsgFilter { get; set; } public string MsgFilter { get; set; }
@ -34,52 +33,46 @@ public class MsgViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.AutoRefresh, x => x.AutoRefresh,
y => y == true) y => y == true)
.Subscribe(c => _config.MsgUIItem.AutoRefresh = AutoRefresh); .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
AppEvents.SendMsgViewRequested AppEvents.SendMsgViewRequested
.AsObservable() .AsObservable()
//.ObserveOn(RxApp.MainThreadScheduler) //.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(content => _ = AppendQueueMsg(content)); .Subscribe(async content => await AppendQueueMsg(content));
} }
private async Task AppendQueueMsg(string msg) private async Task AppendQueueMsg(string msg)
{ {
//if (msg == Global.CommandClearMsg)
//{
// ClearMsg();
// return;
//}
if (AutoRefresh == false) if (AutoRefresh == false)
{ {
return; return;
} }
_ = EnqueueQueueMsg(msg);
EnqueueQueueMsg(msg); if (_blLockShow)
{
return;
}
if (!_config.UiItem.ShowInTaskbar) if (!_config.UiItem.ShowInTaskbar)
{ {
return; return;
} }
if (Interlocked.CompareExchange(ref _showLock, 1, 0) != 0) _blLockShow = true;
{
return;
}
try await Task.Delay(500);
{ var txt = string.Join("", _queueMsg.ToArray());
await Task.Delay(500).ConfigureAwait(false); await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt);
var sb = new StringBuilder(); _blLockShow = false;
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 //filter msg
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) 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); _queueMsg.Enqueue(msg);
if (!msg.EndsWith(Environment.NewLine)) if (!msg.EndsWith(Environment.NewLine))
{ {
_queueMsg.Enqueue(Environment.NewLine); _queueMsg.Enqueue(Environment.NewLine);
} }
await Task.CompletedTask;
} }
//public void ClearMsg() public void ClearMsg()
//{ {
// _queueMsg.Clear(); _queueMsg.Clear();
//} }
private void DoMsgFilter() private void DoMsgFilter()
{ {

View file

@ -285,7 +285,7 @@ public class ProfilesViewModel : MyReactiveObject
private void Reload() private void Reload()
{ {
AppEvents.ReloadRequested.Publish(); AppEvents.ReloadRequested.OnNext(Unit.Default);
} }
public async Task SetSpeedTestResult(SpeedTestResult result) public async Task SetSpeedTestResult(SpeedTestResult result)
@ -371,7 +371,7 @@ public class ProfilesViewModel : MyReactiveObject
public async Task RefreshServers() public async Task RefreshServers()
{ {
AppEvents.ProfilesRefreshRequested.Publish(); AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
await Task.Delay(200); await Task.Delay(200);
} }

View file

@ -148,17 +148,17 @@ public class StatusBarViewModel : MyReactiveObject
NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () => NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
AppEvents.ShowHideWindowRequested.Publish(null); AppEvents.ShowHideWindowRequested.OnNext(null);
await Task.CompletedTask; await Task.CompletedTask;
}); });
ShowWindowCmd = ReactiveCommand.CreateFromTask(async () => ShowWindowCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
AppEvents.ShowHideWindowRequested.Publish(true); AppEvents.ShowHideWindowRequested.OnNext(true);
await Task.CompletedTask; await Task.CompletedTask;
}); });
HideWindowCmd = ReactiveCommand.CreateFromTask(async () => HideWindowCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
AppEvents.ShowHideWindowRequested.Publish(false); AppEvents.ShowHideWindowRequested.OnNext(false);
await Task.CompletedTask; await Task.CompletedTask;
}); });
@ -274,19 +274,19 @@ public class StatusBarViewModel : MyReactiveObject
private async Task AddServerViaClipboard() private async Task AddServerViaClipboard()
{ {
AppEvents.AddServerViaClipboardRequested.Publish(); AppEvents.AddServerViaClipboardRequested.OnNext(Unit.Default);
await Task.Delay(1000); await Task.Delay(1000);
} }
private async Task AddServerViaScan() private async Task AddServerViaScan()
{ {
AppEvents.AddServerViaScanRequested.Publish(); AppEvents.AddServerViaScanRequested.OnNext(Unit.Default);
await Task.Delay(1000); await Task.Delay(1000);
} }
private async Task UpdateSubscriptionProcess(bool blProxy) private async Task UpdateSubscriptionProcess(bool blProxy)
{ {
AppEvents.SubscriptionsUpdateRequested.Publish(blProxy); AppEvents.SubscriptionsUpdateRequested.OnNext(blProxy);
await Task.Delay(1000); await Task.Delay(1000);
} }
@ -348,7 +348,7 @@ public class StatusBarViewModel : MyReactiveObject
{ {
return; return;
} }
AppEvents.SetDefaultServerRequested.Publish(SelectedServer.ID); AppEvents.SetDefaultServerRequested.OnNext(SelectedServer.ID);
} }
public async Task TestServerAvailability() public async Task TestServerAvailability()
@ -449,7 +449,7 @@ public class StatusBarViewModel : MyReactiveObject
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0) if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
{ {
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting); NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
AppEvents.ReloadRequested.Publish(); AppEvents.ReloadRequested.OnNext(Unit.Default);
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null); _updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
} }
} }
@ -496,7 +496,7 @@ public class StatusBarViewModel : MyReactiveObject
} }
} }
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
AppEvents.ReloadRequested.Publish(); AppEvents.ReloadRequested.OnNext(Unit.Default);
} }
private bool AllowEnableTun() private bool AllowEnableTun()

View file

@ -11,7 +11,6 @@
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<Application.Styles> <Application.Styles>
<semi:SemiTheme /> <semi:SemiTheme />
<semi:AvaloniaEditSemiTheme />
<StyleInclude Source="Assets/GlobalStyles.axaml" /> <StyleInclude Source="Assets/GlobalStyles.axaml" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" /> <StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
<dialogHost:DialogHostStyles /> <dialogHost:DialogHostStyles />

View file

@ -54,7 +54,7 @@ public partial class App : Application
{ {
if (desktop.MainWindow != null) if (desktop.MainWindow != null)
{ {
AppEvents.AddServerViaClipboardRequested.Publish(); AppEvents.AddServerViaClipboardRequested.OnNext(Unit.Default);
await Task.Delay(1000); await Task.Delay(1000);
} }
} }

View file

@ -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();
}
}

View file

@ -2,7 +2,6 @@ global using ServiceLib;
global using ServiceLib.Base; global using ServiceLib.Base;
global using ServiceLib.Common; global using ServiceLib.Common;
global using ServiceLib.Enums; global using ServiceLib.Enums;
global using ServiceLib.Events;
global using ServiceLib.Handler; global using ServiceLib.Handler;
global using ServiceLib.Manager; global using ServiceLib.Manager;
global using ServiceLib.Models; global using ServiceLib.Models;

View file

@ -5,7 +5,6 @@ using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using AvaloniaEdit;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Semi.Avalonia; using Semi.Avalonia;
@ -113,8 +112,7 @@ public class ThemeSettingViewModel : MyReactiveObject
x.OfType<ContextMenu>(), x.OfType<ContextMenu>(),
x.OfType<DataGridRow>(), x.OfType<DataGridRow>(),
x.OfType<ListBoxItem>(), x.OfType<ListBoxItem>(),
x.OfType<HeaderedContentControl>(), x.OfType<HeaderedContentControl>()
x.OfType<TextEditor>()
)); ));
style.Add(new Setter() style.Add(new Setter()
{ {
@ -155,8 +153,7 @@ public class ThemeSettingViewModel : MyReactiveObject
x.OfType<DataGridRow>(), x.OfType<DataGridRow>(),
x.OfType<ListBoxItem>(), x.OfType<ListBoxItem>(),
x.OfType<HeaderedContentControl>(), x.OfType<HeaderedContentControl>(),
x.OfType<WindowNotificationManager>(), x.OfType<WindowNotificationManager>()
x.OfType<TextEditor>()
)); ));
style.Add(new Setter() style.Add(new Setter()
{ {

View file

@ -260,7 +260,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
case EGlobalHotkey.SystemProxySet: case EGlobalHotkey.SystemProxySet:
case EGlobalHotkey.SystemProxyUnchanged: case EGlobalHotkey.SystemProxyUnchanged:
case EGlobalHotkey.SystemProxyPac: case EGlobalHotkey.SystemProxyPac:
AppEvents.SysProxyChangeRequested.Publish((ESysProxyType)((int)e - 1)); AppEvents.SysProxyChangeRequested.OnNext((ESysProxyType)((int)e - 1));
break; break;
} }
} }

View file

@ -2,7 +2,6 @@
x:Class="v2rayN.Desktop.Views.MsgView" x:Class="v2rayN.Desktop.Views.MsgView"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib" xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
@ -71,36 +70,35 @@
Theme="{DynamicResource SimpleToggleSwitch}" /> Theme="{DynamicResource SimpleToggleSwitch}" />
</WrapPanel> </WrapPanel>
<avaloniaEdit:TextEditor <ScrollViewer x:Name="msgScrollViewer" VerticalScrollBarVisibility="Auto">
Name="txtMsg" <SelectableTextBlock
Margin="{StaticResource Margin8}" Name="txtMsg"
IsReadOnly="True" Margin="{StaticResource Margin8}"
VerticalScrollBarVisibility="Auto" VerticalAlignment="Stretch"
WordWrap="True"> Classes="TextArea"
<avaloniaEdit:TextEditor.Options> TextAlignment="Left"
<avaloniaEdit:TextEditorOptions AllowScrollBelowDocument="False"/> TextWrapping="Wrap">
</avaloniaEdit:TextEditor.Options> <SelectableTextBlock.ContextMenu>
<avaloniaEdit:TextEditor.ContextFlyout> <ContextMenu>
<MenuFlyout> <MenuItem
<MenuItem x:Name="menuMsgViewSelectAll"
x:Name="menuMsgViewSelectAll" Click="menuMsgViewSelectAll_Click"
Click="menuMsgViewSelectAll_Click" Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" /> <MenuItem
<MenuItem x:Name="menuMsgViewCopy"
x:Name="menuMsgViewCopy" Click="menuMsgViewCopy_Click"
Click="menuMsgViewCopy_Click" Header="{x:Static resx:ResUI.menuMsgViewCopy}" />
Header="{x:Static resx:ResUI.menuMsgViewCopy}" /> <MenuItem
<MenuItem x:Name="menuMsgViewCopyAll"
x:Name="menuMsgViewCopyAll" Click="menuMsgViewCopyAll_Click"
Click="menuMsgViewCopyAll_Click" Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" />
Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" /> <MenuItem
<MenuItem x:Name="menuMsgViewClear"
x:Name="menuMsgViewClear" Click="menuMsgViewClear_Click"
Click="menuMsgViewClear_Click" Header="{x:Static resx:ResUI.menuMsgViewClear}" />
Header="{x:Static resx:ResUI.menuMsgViewClear}" /> </ContextMenu>
</MenuFlyout> </SelectableTextBlock.ContextMenu>
</avaloniaEdit:TextEditor.ContextFlyout> </SelectableTextBlock>
</avaloniaEdit:TextEditor> </ScrollViewer>
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View file

@ -1,6 +1,6 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
@ -10,11 +10,13 @@ namespace v2rayN.Desktop.Views;
public partial class MsgView : ReactiveUserControl<MsgViewModel> public partial class MsgView : ReactiveUserControl<MsgViewModel>
{ {
//private const int KeepLines = 30; private readonly ScrollViewer _scrollViewer;
public MsgView() public MsgView()
{ {
InitializeComponent(); InitializeComponent();
_scrollViewer = this.FindControl<ScrollViewer>("msgScrollViewer");
ViewModel = new MsgViewModel(UpdateViewHandler); ViewModel = new MsgViewModel(UpdateViewHandler);
this.WhenActivated(disposables => 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.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).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) private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@ -37,8 +34,9 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
if (obj is null) if (obj is null)
return false; return false;
Dispatcher.UIThread.Post(() => ShowMsg(obj), Dispatcher.UIThread.Post(() =>
DispatcherPriority.ApplicationIdle); ShowMsg(obj),
DispatcherPriority.ApplicationIdle);
break; break;
} }
return await Task.FromResult(true); return await Task.FromResult(true);
@ -46,37 +44,23 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
//var lineCount = txtMsg.LineCount; txtMsg.Text = msg.ToString();
//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());
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); _scrollViewer?.ScrollToEnd();
} }
} }
public void ClearMsg() public void ClearMsg()
{ {
txtMsg.Clear(); ViewModel?.ClearMsg();
txtMsg.AppendText("----- Message cleared -----\n"); txtMsg.Text = "";
} }
private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e)
{ {
Dispatcher.UIThread.Post(() => txtMsg.Focus();
{ txtMsg.SelectAll();
txtMsg.TextArea.Focus();
txtMsg.SelectAll();
}, DispatcherPriority.Render);
} }
private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e) private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e)

View file

@ -10,7 +10,7 @@
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<sys:Double x:Key="QrcodeWidth">400</sys:Double> <sys:Double x:Key="QrcodeWidth">500</sys:Double>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="32" RowDefinitions="Auto,Auto"> <Grid Margin="32" RowDefinitions="Auto,Auto">

View file

@ -9,7 +9,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="Avalonia.Controls.DataGrid"> <PackageReference Include="Avalonia.Controls.DataGrid">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>
@ -18,7 +17,6 @@
<PackageReference Include="Avalonia.ReactiveUI" /> <PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="MessageBox.Avalonia" /> <PackageReference Include="MessageBox.Avalonia" />
<PackageReference Include="Semi.Avalonia" /> <PackageReference Include="Semi.Avalonia" />
<PackageReference Include="Semi.Avalonia.AvaloniaEdit" />
<PackageReference Include="Semi.Avalonia.DataGrid"> <PackageReference Include="Semi.Avalonia.DataGrid">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>

View file

@ -2,7 +2,6 @@ global using ServiceLib;
global using ServiceLib.Base; global using ServiceLib.Base;
global using ServiceLib.Common; global using ServiceLib.Common;
global using ServiceLib.Enums; global using ServiceLib.Enums;
global using ServiceLib.Events;
global using ServiceLib.Handler; global using ServiceLib.Handler;
global using ServiceLib.Manager; global using ServiceLib.Manager;
global using ServiceLib.Models; global using ServiceLib.Models;

View file

@ -248,7 +248,7 @@ public partial class MainWindow
case EGlobalHotkey.SystemProxySet: case EGlobalHotkey.SystemProxySet:
case EGlobalHotkey.SystemProxyUnchanged: case EGlobalHotkey.SystemProxyUnchanged:
case EGlobalHotkey.SystemProxyPac: case EGlobalHotkey.SystemProxyPac:
AppEvents.SysProxyChangeRequested.Publish((ESysProxyType)((int)e - 1)); AppEvents.SysProxyChangeRequested.OnNext((ESysProxyType)((int)e - 1));
break; break;
} }
} }

View file

@ -47,22 +47,19 @@ public partial class MsgView
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
if (txtMsg.LineCount > ViewModel?.NumMaxMsg) txtMsg.BeginChange();
{ txtMsg.Text = msg.ToString();
ClearMsg();
}
txtMsg.AppendText(msg.ToString());
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); txtMsg.ScrollToEnd();
} }
txtMsg.EndChange();
} }
public void ClearMsg() public void ClearMsg()
{ {
ViewModel?.ClearMsg();
txtMsg.Clear(); txtMsg.Clear();
txtMsg.AppendText("----- Message cleared -----\n");
} }
private void menuMsgViewSelectAll_Click(object sender, System.Windows.RoutedEventArgs e) private void menuMsgViewSelectAll_Click(object sender, System.Windows.RoutedEventArgs e)

View file

@ -12,7 +12,7 @@
Style="{StaticResource ViewGlobal}" Style="{StaticResource ViewGlobal}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<sys:Double x:Key="QrcodeWidth">400</sys:Double> <sys:Double x:Key="QrcodeWidth">500</sys:Double>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="32"> <Grid Margin="32">