Compare commits

...

11 commits

Author SHA1 Message Date
DHR60
bf50765ae8
Merge ab1dc45ed4 into e96a4818c4 2025-09-23 08:50:12 +00:00
DHR60
ab1dc45ed4 Fix 2025-09-23 16:49:52 +08:00
2dust
e96a4818c4 Optimization and improvement
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-09-23 15:31:19 +08:00
2dust
0377e7ce19 Optimization and improvement, using event subscribers 2025-09-23 14:27:42 +08:00
DHR60
2d41272659 Avoid self-reference 2025-09-23 12:46:59 +08:00
2dust
6929886b3e Optimization and improvement, using event subscribers 2025-09-23 12:08:43 +08:00
2dust
721d70c8c7 Update Directory.Packages.props 2025-09-23 11:39:57 +08:00
2dust
27b45aee83 Optimization and improvement, using event subscribers 2025-09-23 11:39:55 +08:00
DHR60
e7f75010d3 Add chain selection control to group outbounds 2025-09-23 11:24:27 +08:00
DHR60
aa1ccdd01b Refactor 2025-09-23 11:24:17 +08:00
DHR60
b17323c982 Add helper function 2025-09-23 11:11:03 +08:00
24 changed files with 314 additions and 395 deletions

View file

@ -20,11 +20,11 @@
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.10" />
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.10" />
<PackageVersion Include="Splat.NLog" Version="16.2.1" />
<PackageVersion Include="Splat.NLog" Version="17.0.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.14" />
</ItemGroup>
</Project>
</Project>

View file

@ -6,16 +6,22 @@ namespace ServiceLib.Handler;
public static class AppEvents
{
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<ServerSpeedItem> DispatcherStatisticsRequested = 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

@ -1251,7 +1251,7 @@ public static class ConfigHandler
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
{
ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && node.ConfigType < EConfigType.Group && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{
itemSocks = new ProfileItem()
{
@ -1262,7 +1262,7 @@ public static class ConfigHandler
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
};
}
else if (node.ConfigType == EConfigType.Custom && node.ConfigType < EConfigType.Group && node.PreSocksPort > 0)
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
{
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem()

View file

@ -32,19 +32,19 @@ public class ProfileItem : ReactiveObject
public string GetSummary()
{
var summary = $"[{(ConfigType).ToString()}] ";
var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch
{
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
if (ConfigType is EConfigType.Custom or > EConfigType.Group)
if (IsComplex())
{
summary += $"[{CoreType.ToString()}]{Remarks}";
}
else
{
var arrAddr = Address.Contains(':') ? Address.Split(':') : Address.Split('.');
var addr = arrAddr.Length switch
{
> 2 => $"{arrAddr.First()}***{arrAddr.Last()}",
> 1 => $"***{arrAddr.Last()}",
_ => Address
};
summary += $"{Remarks}({addr}:{Port})";
}
return summary;
@ -64,6 +64,51 @@ public class ProfileItem : ReactiveObject
return Network.TrimEx();
}
public bool IsComplex()
{
return ConfigType is EConfigType.Custom or > EConfigType.Group;
}
public bool IsValid()
{
if (IsComplex())
return true;
if (Address.IsNullOrEmpty() || Port is <= 0 or >= 65536)
return false;
switch (ConfigType)
{
case EConfigType.VMess:
if (Id.IsNullOrEmpty() || !Utils.IsGuidByParse(Id))
return false;
break;
case EConfigType.VLESS:
if (Id.IsNullOrEmpty() || (!Utils.IsGuidByParse(Id) && Id.Length > 30))
return false;
if (!Global.Flows.Contains(Flow))
return false;
break;
case EConfigType.Shadowsocks:
if (Id.IsNullOrEmpty())
return false;
if (string.IsNullOrEmpty(Security) || !Global.SsSecuritiesInSingbox.Contains(Security))
return false;
break;
}
if ((ConfigType is EConfigType.VLESS or EConfigType.Trojan)
&& StreamSecurity == Global.StreamSecurityReality
&& PublicKey.IsNullOrEmpty())
{
return false;
}
return true;
}
#endregion function
[PrimaryKey]

View file

@ -15,35 +15,8 @@ public partial class CoreConfigSingboxService(Config config)
var ret = new RetResult();
try
{
if (node?.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
)).Where(p => p != null).ToList();
if (childProfiles.Count <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(childProfiles);
}
}
if (node == null
|| node.Port <= 0)
|| !node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
@ -55,6 +28,17 @@ public partial class CoreConfigSingboxService(Config config)
}
ret.Msg = ResUI.InitialConfiguration;
if (node?.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient);
if (result.IsNullOrEmpty())
@ -169,12 +153,9 @@ public partial class CoreConfigSingboxService(Config config)
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
if (item is null || item.IsComplex() || !item.IsValid())
{
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
continue;
}
//find unused port
@ -214,27 +195,6 @@ public partial class CoreConfigSingboxService(Config config)
singboxConfig.inbounds.Add(inbound);
//outbound
if (item is null)
{
continue;
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS
&& !Global.Flows.Contains(item.Flow))
{
continue;
}
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
continue;
}
var server = await GenServer(item);
if (server is null)
{
@ -293,7 +253,8 @@ public partial class CoreConfigSingboxService(Config config)
var ret = new RetResult();
try
{
if (node is not { Port: > 0 })
if (node == null
|| !node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
@ -371,7 +332,7 @@ public partial class CoreConfigSingboxService(Config config)
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
@ -405,53 +366,12 @@ public partial class CoreConfigSingboxService(Config config)
await GenRouting(singboxConfig);
await GenExperimental(singboxConfig);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (it.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
var itemGroup = await AppManager.Instance.GetProfileItem(it.IndexId);
proxyProfiles.Add(itemGroup);
}
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenOutboundsListWithChain(proxyProfiles, singboxConfig, multipleLoad);
await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);
@ -469,7 +389,7 @@ public partial class CoreConfigSingboxService(Config config)
}
}
public async Task<RetResult> GenerateClientChainConfig(List<ProfileItem> selecteds)
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
try
@ -503,48 +423,12 @@ public partial class CoreConfigSingboxService(Config config)
await GenExperimental(singboxConfig);
singboxConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
var groupRet = await GenGroupOutbound(parentNode, singboxConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenChainOutboundsList(proxyProfiles, singboxConfig);
await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig);

View file

@ -204,6 +204,67 @@ public partial class CoreConfigSingboxService
return await Task.FromResult<BaseServer4Sbox?>(null);
}
private async Task<int> GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain))
{
return -1;
}
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem is null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return -1;
}
// remove custom nodes
// remove group nodes for proxy chain
// avoid self-reference
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null
&& p.IsValid()
&& p.ConfigType != EConfigType.Custom
&& (node.ConfigType == EConfigType.PolicyGroup || p.ConfigType < EConfigType.Group)
&& p.IndexId != node.IndexId
)
.ToList();
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName);
break;
default:
break;
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenOutboundMux(ProfileItem node, Outbound4Sbox outbound)
{
try

View file

@ -385,29 +385,8 @@ public partial class CoreConfigSingboxService
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return Global.ProxyTag;
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
)).Where(p => p != null).ToList();
if (childProfiles.Count <= 0)
{
return Global.ProxyTag;
}
var childBaseTagName = $"{Global.ProxyTag}-{node.IndexId}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, singboxConfig, profileGroupItem.MultipleLoad, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
var ret = await GenGroupOutbound(node, singboxConfig, childBaseTagName);
if (ret == 0)
{
return childBaseTagName;

View file

@ -15,35 +15,8 @@ public partial class CoreConfigV2rayService(Config config)
var ret = new RetResult();
try
{
if (node?.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
)).Where(p => p != null).ToList();
if (childProfiles.Count <= 0)
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(childProfiles);
}
}
if (node == null
|| node.Port <= 0)
|| !node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;
@ -57,6 +30,17 @@ public partial class CoreConfigV2rayService(Config config)
ret.Msg = ResUI.InitialConfiguration;
if (node?.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(node);
case EConfigType.ProxyChain:
return await GenerateClientChainConfig(node);
}
}
var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient);
if (result.IsNullOrEmpty())
{
@ -98,7 +82,7 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
public async Task<RetResult> GenerateClientMultipleLoadConfig(ProfileItem parentNode)
{
var ret = new RetResult();
@ -134,57 +118,12 @@ public partial class CoreConfigV2rayService(Config config)
await GenDns(null, v2rayConfig);
await GenStatistic(v2rayConfig);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (it.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
var itemGroup = await AppManager.Instance.GetProfileItem(it.IndexId);
proxyProfiles.Add(itemGroup);
}
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenOutboundsListWithChain(proxyProfiles, v2rayConfig);
//add balancers
await GenObservatory(v2rayConfig, multipleLoad);
await GenBalancer(v2rayConfig, multipleLoad);
var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}";
@ -248,7 +187,7 @@ public partial class CoreConfigV2rayService(Config config)
}
}
public async Task<RetResult> GenerateClientChainConfig(List<ProfileItem> selecteds)
public async Task<RetResult> GenerateClientChainConfig(ProfileItem parentNode)
{
var ret = new RetResult();
@ -284,48 +223,12 @@ public partial class CoreConfigV2rayService(Config config)
await GenStatistic(v2rayConfig);
v2rayConfig.outbounds.RemoveAt(0);
var proxyProfiles = new List<ProfileItem>();
foreach (var it in selecteds)
{
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
{
continue;
}
if (it.Port <= 0)
{
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (item is null)
{
continue;
}
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
{
if (item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInSingbox.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS && !Global.Flows.Contains(item.Flow))
{
continue;
}
//outbound
proxyProfiles.Add(item);
}
if (proxyProfiles.Count <= 0)
var groupRet = await GenGroupOutbound(parentNode, v2rayConfig);
if (groupRet != 0)
{
ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret;
}
await GenChainOutboundsList(proxyProfiles, v2rayConfig);
ret.Success = true;
@ -398,12 +301,9 @@ public partial class CoreConfigV2rayService(Config config)
continue;
}
var item = await AppManager.Instance.GetProfileItem(it.IndexId);
if (it.ConfigType is EConfigType.VMess or EConfigType.VLESS)
if (item is null || item.IsComplex() || !item.IsValid())
{
if (item is null || item.Id.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Id))
{
continue;
}
continue;
}
//find unused port
@ -432,28 +332,6 @@ public partial class CoreConfigV2rayService(Config config)
it.Port = port;
it.AllowTest = true;
//outbound
if (item is null)
{
continue;
}
if (item.ConfigType == EConfigType.Shadowsocks
&& !Global.SsSecuritiesInXray.Contains(item.Security))
{
continue;
}
if (item.ConfigType == EConfigType.VLESS
&& !Global.Flows.Contains(item.Flow))
{
continue;
}
if (it.ConfigType is EConfigType.VLESS or EConfigType.Trojan
&& item.StreamSecurity == Global.StreamSecurityReality
&& item.PublicKey.IsNullOrEmpty())
{
continue;
}
//inbound
Inbounds4Ray inbound = new()
{
@ -464,6 +342,7 @@ public partial class CoreConfigV2rayService(Config config)
inbound.tag = inbound.protocol + inbound.port.ToString();
v2rayConfig.inbounds.Add(inbound);
//outbound
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
await GenOutbound(item, outbound);
outbound.tag = Global.ProxyTag + inbound.port.ToString();
@ -497,7 +376,8 @@ public partial class CoreConfigV2rayService(Config config)
var ret = new RetResult();
try
{
if (node is not { Port: > 0 })
if (node == null
|| !node.IsValid())
{
ret.Msg = ResUI.CheckServerSettings;
return ret;

View file

@ -480,6 +480,69 @@ public partial class CoreConfigV2rayService
return 0;
}
private async Task<int> GenGroupOutbound(ProfileItem node, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false)
{
try
{
if (node.ConfigType is not (EConfigType.PolicyGroup or EConfigType.ProxyChain))
{
return -1;
}
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem is null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return -1;
}
// remove custom nodes
// remove group nodes for proxy chain
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
))
.Where(p =>
p != null &&
p.IsValid() &&
p.ConfigType != EConfigType.Custom &&
(node.ConfigType == EConfigType.PolicyGroup || p.ConfigType < EConfigType.Group)
)
.ToList();
if (childProfiles.Count <= 0)
{
return -1;
}
switch (node.ConfigType)
{
case EConfigType.PolicyGroup:
if (ignoreOriginChain)
{
await GenOutboundsList(childProfiles, v2rayConfig, baseTagName);
}
else
{
await GenOutboundsListWithChain(childProfiles, v2rayConfig, baseTagName);
}
break;
case EConfigType.ProxyChain:
await GenChainOutboundsList(childProfiles, v2rayConfig, baseTagName);
break;
default:
break;
}
//add balancers
await GenObservatory(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName);
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, baseTagName);
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
return await Task.FromResult(0);
}
private async Task<int> GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig)
{
//fragment proxy

View file

@ -141,33 +141,8 @@ public partial class CoreConfigV2rayService
if (node.ConfigType is EConfigType.PolicyGroup or EConfigType.ProxyChain)
{
ProfileGroupItemManager.Instance.TryGet(node.IndexId, out var profileGroupItem);
if (profileGroupItem == null || profileGroupItem.ChildItems.IsNullOrEmpty())
{
return Global.ProxyTag;
}
var childProfiles = (await Task.WhenAll(
Utils.String2List(profileGroupItem.ChildItems)
.Where(p => !p.IsNullOrEmpty())
.Select(AppManager.Instance.GetProfileItem)
)).Where(p => p != null).ToList();
if (childProfiles.Count <= 0)
{
return Global.ProxyTag;
}
var childBaseTagName = $"{Global.ProxyTag}-{node.IndexId}";
var ret = node.ConfigType switch
{
EConfigType.PolicyGroup =>
await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName),
EConfigType.ProxyChain =>
await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName),
_ => throw new NotImplementedException()
};
if (node.ConfigType == EConfigType.PolicyGroup)
{
await GenBalancer(v2rayConfig, profileGroupItem.MultipleLoad, childBaseTagName);
}
var ret = await GenGroupOutbound(node, v2rayConfig, childBaseTagName);
if (ret == 0)
{
return childBaseTagName;

View file

@ -69,6 +69,8 @@ public class ClashProxiesViewModel : MyReactiveObject
SortingSelected = _config.ClashUIItem.ProxiesSorting;
RuleModeSelected = (int)_config.ClashUIItem.RuleMode;
#region WhenAnyValue && ReactiveCommand
this.WhenAnyValue(
x => x.SelectedGroup,
y => y != null && y.Name.IsNotEmpty())
@ -89,6 +91,17 @@ public class ClashProxiesViewModel : MyReactiveObject
y => y == true)
.Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; });
#endregion WhenAnyValue && ReactiveCommand
#region AppEvents
AppEvents.ProxiesReloadRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await ProxiesReload());
#endregion AppEvents
_ = Init();
}

View file

@ -2,7 +2,6 @@ using System.Reactive;
using System.Reactive.Concurrency;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Splat;
namespace ServiceLib.ViewModels;
@ -251,7 +250,6 @@ public class MainWindowViewModel : MyReactiveObject
BlReloadEnabled = true;
await Reload();
await AutoHideStartup();
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
}
#endregion Init
@ -312,7 +310,7 @@ public class MainWindowViewModel : MyReactiveObject
private void RefreshSubscriptions()
{
Locator.Current.GetService<ProfilesViewModel>()?.RefreshSubscriptions();
AppEvents.SubscriptionsRefreshRequested.OnNext(Unit.Default);
}
#endregion Servers && Groups
@ -448,7 +446,7 @@ public class MainWindowViewModel : MyReactiveObject
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
if (ret == true)
{
Locator.Current.GetService<StatusBarViewModel>()?.InboundDisplayStatus();
AppEvents.InboundDisplayRequested.OnNext(Unit.Default);
await Reload();
}
}
@ -459,7 +457,7 @@ public class MainWindowViewModel : MyReactiveObject
if (ret == true)
{
await ConfigHandler.InitBuiltinRouting(_config);
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
AppEvents.RoutingsMenuRefreshRequested.OnNext(Unit.Default);
await Reload();
}
}
@ -533,9 +531,15 @@ public class MainWindowViewModel : MyReactiveObject
await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000);
});
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
AppEvents.TestServerRequested.OnNext(Unit.Default);
RxApp.MainThreadScheduler.Schedule(() => _ = ReloadResult());
var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
if (showClashUI)
{
AppEvents.ProxiesReloadRequested.OnNext(Unit.Default);
}
RxApp.MainThreadScheduler.Schedule(() => ReloadResult(showClashUI));
BlReloadEnabled = true;
if (_hasNextReloadJob)
@ -545,19 +549,11 @@ public class MainWindowViewModel : MyReactiveObject
}
}
public async Task ReloadResult()
private void ReloadResult(bool showClashUI)
{
// BlReloadEnabled = true;
//Locator.Current.GetService<StatusBarViewModel>()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false);
ShowClashUI = _config.IsRunningCore(ECoreType.sing_box);
if (ShowClashUI)
{
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
}
else
{
TabMainSelectedIndex = 0;
}
ShowClashUI = showClashUI;
TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0;
}
private async Task LoadCore()
@ -589,7 +585,7 @@ public class MainWindowViewModel : MyReactiveObject
{
await ConfigHandler.ApplyRegionalPreset(_config, type);
await ConfigHandler.InitRouting(_config);
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
AppEvents.RoutingsMenuRefreshRequested.OnNext(Unit.Default);
await ConfigHandler.SaveConfig(_config);
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);

View file

@ -250,11 +250,21 @@ public class ProfilesViewModel : MyReactiveObject
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await RefreshServersBiz());
AppEvents.SubscriptionsRefreshRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await RefreshSubscriptions());
AppEvents.DispatcherStatisticsRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async result => await UpdateStatistics(result));
AppEvents.SetDefaultServerRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async indexId => await SetDefaultServer(indexId));
#endregion AppEvents
_ = Init();
@ -390,7 +400,7 @@ public class ProfilesViewModel : MyReactiveObject
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
}
public async Task RefreshSubscriptions()
private async Task RefreshSubscriptions()
{
SubItems.Clear();
@ -579,7 +589,7 @@ public class ProfilesViewModel : MyReactiveObject
await SetDefaultServer(SelectedProfile.IndexId);
}
public async Task SetDefaultServer(string? indexId)
private async Task SetDefaultServer(string? indexId)
{
if (indexId.IsNullOrEmpty())
{

View file

@ -11,6 +11,9 @@ namespace ServiceLib.ViewModels;
public class StatusBarViewModel : MyReactiveObject
{
private static readonly Lazy<StatusBarViewModel> _instance = new(() => new(null));
public static StatusBarViewModel Instance => _instance.Value;
#region ObservableCollection
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
@ -209,6 +212,26 @@ public class StatusBarViewModel : MyReactiveObject
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async result => await UpdateStatistics(result));
AppEvents.RoutingsMenuRefreshRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await RefreshRoutingsMenu());
AppEvents.TestServerRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await TestServerAvailability());
AppEvents.InboundDisplayRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await InboundDisplayStatus());
AppEvents.SysProxyChangeRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async result => await SetListenerType(result));
#endregion AppEvents
_ = Init();
@ -329,7 +352,7 @@ public class StatusBarViewModel : MyReactiveObject
{
return;
}
Locator.Current.GetService<ProfilesViewModel>()?.SetDefaultServer(SelectedServer.ID);
AppEvents.SetDefaultServerRequested.OnNext(SelectedServer.ID);
}
public async Task TestServerAvailability()
@ -364,7 +387,7 @@ public class StatusBarViewModel : MyReactiveObject
#region System proxy and Routings
public async Task SetListenerType(ESysProxyType type)
private async Task SetListenerType(ESysProxyType type)
{
if (_config.SystemProxyItem.SysProxyType == type)
{
@ -393,7 +416,7 @@ public class StatusBarViewModel : MyReactiveObject
}
}
public async Task RefreshRoutingsMenu()
private async Task RefreshRoutingsMenu()
{
RoutingItems.Clear();
@ -501,7 +524,7 @@ public class StatusBarViewModel : MyReactiveObject
#region UI
public async Task InboundDisplayStatus()
private async Task InboundDisplayStatus()
{
StringBuilder sb = new();
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");

View file

@ -16,9 +16,7 @@ public partial class App : Application
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
var ViewModel = new StatusBarViewModel(null);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(StatusBarViewModel));
DataContext = ViewModel;
DataContext = StatusBarViewModel.Instance;
}
public override void OnFrameworkInitializationCompleted()

View file

@ -3,7 +3,6 @@ using Avalonia.Input;
using Avalonia.ReactiveUI;
using DynamicData;
using ReactiveUI;
using Splat;
namespace v2rayN.Desktop.Views;
@ -13,7 +12,6 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
{
InitializeComponent();
ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ClashProxiesViewModel));
lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped;
this.KeyDown += ClashProxiesView_KeyDown;

View file

@ -20,7 +20,7 @@ namespace v2rayN.Desktop.Views;
public partial class MainWindow : WindowBase<MainWindowViewModel>
{
private static Config _config;
private WindowNotificationManager? _manager;
private readonly WindowNotificationManager? _manager;
private CheckUpdateView? _checkUpdateView;
private BackupAndRestoreView? _backupAndRestoreView;
private bool _blCloseByUser = false;
@ -266,7 +266,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
case EGlobalHotkey.SystemProxySet:
case EGlobalHotkey.SystemProxyUnchanged:
case EGlobalHotkey.SystemProxyPac:
Locator.Current.GetService<StatusBarViewModel>()?.SetListenerType((ESysProxyType)((int)e - 1));
AppEvents.SysProxyChangeRequested.OnNext((ESysProxyType)((int)e - 1));
break;
}
}

View file

@ -8,7 +8,6 @@ using Avalonia.Threading;
using DialogHostAvalonia;
using MsBox.Avalonia.Enums;
using ReactiveUI;
using Splat;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views;
@ -48,7 +47,6 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
//}
ViewModel = new ProfilesViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ProfilesViewModel));
this.WhenActivated(disposables =>
{

View file

@ -6,7 +6,6 @@ using Avalonia.ReactiveUI;
using Avalonia.Threading;
using DialogHostAvalonia;
using ReactiveUI;
using Splat;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views;
@ -20,9 +19,8 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
InitializeComponent();
_config = AppManager.Instance.Config;
//ViewModel = new StatusBarViewModel(UpdateViewHandler);
//Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(StatusBarViewModel));
ViewModel = Locator.Current.GetService<StatusBarViewModel>();
ViewModel = StatusBarViewModel.Instance;
ViewModel?.InitUpdateView(UpdateViewHandler);
txtRunningServerDisplay.Tapped += TxtRunningServerDisplay_Tapped;

View file

@ -59,6 +59,7 @@ public partial class AddGroupServerWindow
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
});
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)

View file

@ -1,7 +1,6 @@
using System.Reactive.Disposables;
using System.Windows.Input;
using ReactiveUI;
using Splat;
namespace v2rayN.Views;
@ -14,7 +13,6 @@ public partial class ClashProxiesView
{
InitializeComponent();
ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ClashProxiesViewModel));
lstProxyDetails.PreviewMouseDoubleClick += lstProxyDetails_PreviewMouseDoubleClick;
this.WhenActivated(disposables =>

View file

@ -256,7 +256,7 @@ public partial class MainWindow
case EGlobalHotkey.SystemProxySet:
case EGlobalHotkey.SystemProxyUnchanged:
case EGlobalHotkey.SystemProxyPac:
Locator.Current.GetService<StatusBarViewModel>()?.SetListenerType((ESysProxyType)((int)e - 1));
AppEvents.SysProxyChangeRequested.OnNext((ESysProxyType)((int)e - 1));
break;
}
}
@ -294,11 +294,7 @@ public partial class MainWindow
var clipboardData = WindowsUtils.GetClipboardData();
if (clipboardData.IsNotEmpty())
{
var service = Locator.Current.GetService<MainWindowViewModel>();
if (service != null)
{
_ = service.AddServerViaClipboardAsync(clipboardData);
}
ViewModel?.AddServerViaClipboardAsync(clipboardData);
}
break;

View file

@ -8,7 +8,6 @@ using System.Windows.Media;
using System.Windows.Threading;
using MaterialDesignThemes.Wpf;
using ReactiveUI;
using Splat;
using v2rayN.Base;
using Point = System.Windows.Point;
@ -42,7 +41,6 @@ public partial class ProfilesView
}
ViewModel = new ProfilesViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ProfilesViewModel));
this.WhenActivated(disposables =>
{

View file

@ -3,7 +3,6 @@ using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using ReactiveUI;
using Splat;
using v2rayN.Manager;
namespace v2rayN.Views;
@ -16,8 +15,8 @@ public partial class StatusBarView
{
InitializeComponent();
_config = AppManager.Instance.Config;
ViewModel = new StatusBarViewModel(UpdateViewHandler);
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(StatusBarViewModel));
ViewModel = StatusBarViewModel.Instance;
ViewModel?.InitUpdateView(UpdateViewHandler);
menuExit.Click += menuExit_Click;
txtRunningServerDisplay.PreviewMouseDown += txtRunningInfoDisplay_MouseDoubleClick;