Compare commits

...

3 commits

Author SHA1 Message Date
DHR60
32334ada6b
Merge 4301415b4c into d86003df55 2025-09-26 00:48:29 +08:00
2dust
d86003df55 Optimize and improve the Subject
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
2025-09-25 10:56:10 +08:00
DHR60
4301415b4c Add rule type selection to routing rules 2025-09-23 17:59:18 +08:00
26 changed files with 215 additions and 84 deletions

View file

@ -0,0 +1,32 @@
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

@ -0,0 +1,29 @@
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

@ -87,6 +87,8 @@ public class Global
public const string SingboxFinalResolverTag = "final_resolver";
public const string SingboxHostsDNSTag = "hosts_dns";
public const string SingboxFakeDNSTag = "fake_dns";
public const string RoutingRuleType = "Routing";
public const string DNSRuleType = "DNS";
public static readonly List<string> IEProxyProtocols =
[
@ -471,6 +473,12 @@ public class Global
"tcp,udp"
];
public static readonly List<string> RuleTypes =
[
RoutingRuleType,
DNSRuleType
];
public static readonly List<string> destOverrideProtocols =
[
"http",

View file

@ -1,6 +1,7 @@
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;

View file

@ -1,33 +0,0 @@
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

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

View file

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

View file

@ -15,4 +15,5 @@ public class RulesItem
public List<string>? Process { get; set; }
public bool Enabled { get; set; } = true;
public string? Remarks { get; set; }
public List<string>? RuleTypes { get; set; }
}

View file

@ -253,6 +253,11 @@ public partial class CoreConfigSingboxService
continue;
}
if ((item.RuleTypes?.Count ?? 0) > 0 && !item.RuleTypes.Contains(Global.DNSRuleType))
{
continue;
}
var rule = new Rule4Sbox();
var validDomains = item.Domain.Count(it => ParseV2Domain(it, rule));
if (validDomains <= 0)

View file

@ -136,13 +136,21 @@ public partial class CoreConfigSingboxService
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item1 in rules ?? [])
{
if (item1.Enabled)
if (!item1.Enabled)
{
await GenRoutingUserRule(item1, singboxConfig);
if (item1.Ip != null && item1.Ip.Count > 0)
{
ipRules.Add(item1);
}
continue;
}
if ((item1.RuleTypes?.Count ?? 0) > 0 && !item1.RuleTypes.Contains(Global.RoutingRuleType))
{
continue;
}
await GenRoutingUserRule(item1, singboxConfig);
if (item1.Ip?.Count > 0)
{
ipRules.Add(item1);
}
}
}

View file

@ -142,6 +142,11 @@ public partial class CoreConfigV2rayService
continue;
}
if ((item.RuleTypes?.Count ?? 0) > 0 && !item.RuleTypes.Contains(Global.DNSRuleType))
{
continue;
}
foreach (var domain in item.Domain)
{
if (domain.StartsWith('#'))

View file

@ -20,11 +20,18 @@ public partial class CoreConfigV2rayService
var rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet);
foreach (var item in rules)
{
if (item.Enabled)
if (!item.Enabled)
{
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig);
continue;
}
if ((item.RuleTypes?.Count ?? 0) > 0 && !item.RuleTypes.Contains(Global.RoutingRuleType))
{
continue;
}
var item2 = JsonUtils.Deserialize<RulesItem4Ray>(JsonUtils.Serialize(item));
await GenRoutingUserRule(item2, v2rayConfig);
}
}
}

View file

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

View file

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

View file

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

View file

@ -21,6 +21,8 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
[Reactive]
public string Process { get; set; }
public IList<string> Types { get; set; }
[Reactive]
public bool AutoSort { get; set; }
@ -51,6 +53,11 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
Domain = Utils.List2String(SelectedSource.Domain, true);
IP = Utils.List2String(SelectedSource.Ip, true);
Process = Utils.List2String(SelectedSource.Process, true);
Types = SelectedSource.RuleTypes?.ToList();
if (Types == null || Types.Count == 0)
{
Types = Global.RuleTypes;
}
}
private async Task SaveRulesAsync()
@ -73,6 +80,7 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
}
SelectedSource.Protocol = ProtocolItems?.ToList();
SelectedSource.InboundTag = InboundTagItems?.ToList();
SelectedSource.RuleTypes = Types?.ToList();
var hasRule = SelectedSource.Domain?.Count > 0
|| SelectedSource.Ip?.Count > 0

View file

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

View file

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

View file

@ -2,6 +2,7 @@ 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;

View file

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

View file

@ -33,13 +33,25 @@
Width="300"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<ToggleSwitch
x:Name="togEnabled"
<StackPanel
Grid.Row="0"
Grid.Column="2"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleSwitch
x:Name="togEnabled"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<ListBox
x:Name="clbRuleTypes"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
SelectionMode="Multiple,Toggle"
Theme="{DynamicResource CardCheckGroupListBox}" />
</StackPanel>
<TextBlock
Grid.Row="1"
@ -58,17 +70,15 @@
Grid.Row="1"
Grid.Column="2"
Margin="{StaticResource Margin4}"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Center">
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="btnSelectProfile"
Margin="0,0,8,0"
Content="{x:Static resx:ResUI.TbSelectProfile}"
Click="BtnSelectProfile_Click" />
<TextBlock
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
Click="BtnSelectProfile_Click"
Content="{x:Static resx:ResUI.TbSelectProfile}" />
<TextBlock VerticalAlignment="Center" Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
</StackPanel>
<TextBlock

View file

@ -29,6 +29,16 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
clbInboundTag.ItemsSource = Global.InboundTags;
cmbNetwork.ItemsSource = Global.RuleNetworks;
clbRuleTypes.SelectionChanged += ClbRuleTypes_SelectionChanged;
clbRuleTypes.ItemsSource = Global.RuleTypes;
if (ViewModel.Types != null)
{
foreach (var it in ViewModel.Types)
{
clbRuleTypes.SelectedItems.Add(it);
}
}
if (!rulesItem.Id.IsNullOrEmpty())
{
rulesItem.Protocol?.ForEach(it =>
@ -108,4 +118,12 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
}
}
}
private void ClbRuleTypes_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.Types = clbRuleTypes.SelectedItems.Cast<string>().ToList();
}
}
}

View file

@ -2,6 +2,7 @@ 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;

View file

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

View file

@ -48,13 +48,25 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
Style="{StaticResource DefTextBox}" />
<ToggleButton
x:Name="togEnabled"
<StackPanel
Grid.Row="0"
Grid.Column="2"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleButton
x:Name="togEnabled"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<ListBox
x:Name="clbRuleTypes"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilterChipPrimaryListBox}" />
</StackPanel>
<TextBlock
Grid.Row="1"

View file

@ -22,6 +22,16 @@ public partial class RoutingRuleDetailsWindow
clbInboundTag.ItemsSource = Global.InboundTags;
cmbNetwork.ItemsSource = Global.RuleNetworks;
clbRuleTypes.SelectionChanged += ClbRuleTypes_SelectionChanged;
clbRuleTypes.ItemsSource = Global.RuleTypes;
if (ViewModel.Types != null)
{
foreach (var it in ViewModel.Types)
{
clbRuleTypes.SelectedItems.Add(it);
}
}
if (!rulesItem.Id.IsNullOrEmpty())
{
rulesItem.Protocol?.ForEach(it =>
@ -101,4 +111,12 @@ public partial class RoutingRuleDetailsWindow
}
}
}
private void ClbRuleTypes_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.Types = clbRuleTypes.SelectedItems.Cast<string>().ToList();
}
}
}