mirror of
https://github.com/2dust/v2rayN.git
synced 2026-08-09 00:32:04 +03:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5efff37acb | ||
|
|
e8cdd1cc02 | ||
|
|
d381d429e4 | ||
|
|
b27ae11d1e | ||
|
|
eff584597f | ||
|
|
ccf18ba0fb | ||
|
|
15ba2bdf93 | ||
|
|
e1e6c5ddb0 | ||
|
|
d924c6f557 |
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.24.4</Version>
|
||||
<Version>7.24.5</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.0.3" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.0" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.5" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI" Version="24.0.0" />
|
||||
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.1.0" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="24.0.0" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0" />
|
||||
|
||||
@@ -150,6 +150,23 @@ internal static class CoreConfigTestFactory
|
||||
};
|
||||
}
|
||||
|
||||
public static ProfileItem CreateCustomOutboundNode(ECoreType coreType, string indexId = "node-custom-1",
|
||||
string remarks = "demo-custom-outbound", string address = "custom_outbound.json")
|
||||
{
|
||||
return new ProfileItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
ConfigType = EConfigType.Outbound,
|
||||
CoreType = coreType,
|
||||
Remarks = remarks,
|
||||
Address = address,
|
||||
Port = 0,
|
||||
Network = nameof(ETransport.raw),
|
||||
StreamSecurity = string.Empty,
|
||||
Subid = string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public static ProfileItem CreatePolicyGroupNode(ECoreType coreType, string indexId, string remarks,
|
||||
IEnumerable<string> childIndexIds)
|
||||
{
|
||||
|
||||
@@ -626,4 +626,40 @@ public class CoreConfigSingboxServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||
{
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var customNode = CoreConfigTestFactory.CreateCustomOutboundNode(ECoreType.sing_box, "n-custom", "custom-singbox");
|
||||
var customJsonContent = """
|
||||
{
|
||||
"type": "shadowsocks",
|
||||
"server": "1.2.3.4",
|
||||
"server_port": 8388,
|
||||
"method": "aes-128-gcm",
|
||||
"password": "custom_password"
|
||||
}
|
||||
""";
|
||||
|
||||
var context = CoreConfigTestFactory.CreateContext(config, customNode, ECoreType.sing_box);
|
||||
context.CustomOutboundContent[customNode.IndexId] = customJsonContent;
|
||||
|
||||
var result = new CoreConfigSingboxService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
result.Data.Should().NotBeNull();
|
||||
|
||||
var cfg = JsonUtils.Deserialize<SingboxConfig>(result.Data!.ToString());
|
||||
cfg.Should().NotBeNull();
|
||||
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||
proxyOutbound.Should().NotBeNull();
|
||||
proxyOutbound!.type.Should().Be("shadowsocks");
|
||||
proxyOutbound.server.Should().Be("1.2.3.4");
|
||||
proxyOutbound.server_port.Should().Be(8388);
|
||||
proxyOutbound.method.Should().Be("aes-128-gcm");
|
||||
proxyOutbound.password.Should().Be("custom_password");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,4 +592,43 @@ public class CoreConfigV2rayServiceTests
|
||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.0/32");
|
||||
tunInbound!.settings.autoSystemRoutingTable.Should().Contain("10.0.0.2/31");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||
{
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var customNode = CoreConfigTestFactory.CreateCustomOutboundNode(ECoreType.Xray, "n-custom", "custom-xray");
|
||||
var customJsonContent = """
|
||||
{
|
||||
"protocol": "shadowsocks",
|
||||
"settings": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "1.2.3.4",
|
||||
"port": 8388,
|
||||
"method": "aes-128-gcm",
|
||||
"password": "custom_password"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var context = CoreConfigTestFactory.CreateContext(config, customNode, ECoreType.Xray);
|
||||
context.CustomOutboundContent[customNode.IndexId] = customJsonContent;
|
||||
|
||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue($"ret msg: {result.Msg}");
|
||||
result.Data.Should().NotBeNull();
|
||||
|
||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString());
|
||||
cfg.Should().NotBeNull();
|
||||
var proxyOutbound = cfg!.outbounds.FirstOrDefault(o => o.tag == Global.ProxyTag);
|
||||
proxyOutbound.Should().NotBeNull();
|
||||
proxyOutbound!.protocol.Should().Be("shadowsocks");
|
||||
proxyOutbound.settings.servers.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@ global using System.Diagnostics;
|
||||
global using System.Net;
|
||||
global using System.Net.NetworkInformation;
|
||||
global using System.Net.Sockets;
|
||||
global using System.Reactive;
|
||||
global using System.Reactive.Disposables;
|
||||
global using System.Reactive.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Security.Cryptography;
|
||||
@@ -15,10 +12,7 @@ global using System.Text.Json;
|
||||
global using System.Text.Json.Nodes;
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using DynamicData;
|
||||
global using DynamicData.Binding;
|
||||
global using ReactiveUI;
|
||||
global using ReactiveUI.Fody.Helpers;
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
|
||||
66
v2rayN/ServiceLib/Base/BulkObservableCollection.cs
Normal file
66
v2rayN/ServiceLib/Base/BulkObservableCollection.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public class BulkObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
private bool _suppressNotification = false;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T>? collection)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_suppressNotification = true;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressNotification = false;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Replace(T oldItem, T newItem)
|
||||
{
|
||||
var index = Items.IndexOf(oldItem);
|
||||
if (index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Items[index] = newItem;
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace,
|
||||
newItem,
|
||||
oldItem,
|
||||
index));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public static class Extension
|
||||
|
||||
public static bool IsComplexType(this EConfigType configType)
|
||||
{
|
||||
return configType is EConfigType.Custom or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||
return configType is EConfigType.Custom or EConfigType.Outbound or EConfigType.PolicyGroup or EConfigType.ProxyChain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ public enum EConfigType
|
||||
HTTP = 10,
|
||||
Anytls = 11,
|
||||
Naive = 12,
|
||||
Outbound = 13,
|
||||
PolicyGroup = 101,
|
||||
ProxyChain = 102,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ServiceLib.Events;
|
||||
|
||||
public static class AppEvents
|
||||
{
|
||||
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
||||
public static readonly EventChannel<RxVoid> AddServerViaClipboardRequested = new();
|
||||
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
||||
|
||||
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||
@@ -10,7 +10,7 @@ public static class AppEvents
|
||||
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<RxVoid> AppExitRequested = new();
|
||||
public static readonly EventChannel<bool> ShutdownRequested = new();
|
||||
|
||||
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace ServiceLib.Events;
|
||||
|
||||
public sealed class EventChannel<T>
|
||||
{
|
||||
private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>());
|
||||
private readonly Signal<T> _signal = new();
|
||||
private readonly Lock _gate = new();
|
||||
private readonly IObservable<T> _observable;
|
||||
public EventChannel()
|
||||
{
|
||||
_observable = _signal.Synchronize(_gate);
|
||||
}
|
||||
|
||||
public IObservable<T> AsObservable()
|
||||
{
|
||||
return _subject.AsObservable();
|
||||
return _observable;
|
||||
}
|
||||
|
||||
public void Publish(T value)
|
||||
{
|
||||
_subject.OnNext(value);
|
||||
lock (_gate)
|
||||
{
|
||||
_signal.OnNext(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish()
|
||||
{
|
||||
if (typeof(T) != typeof(Unit))
|
||||
if (typeof(T) != typeof(RxVoid))
|
||||
{
|
||||
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>.");
|
||||
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<RxVoid>.");
|
||||
}
|
||||
lock (_gate)
|
||||
{
|
||||
_signal.OnNext((T)(object)RxVoid.Default);
|
||||
}
|
||||
_subject.OnNext((T)(object)Unit.Default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ReactiveUI />
|
||||
</Weavers>
|
||||
@@ -1,11 +1,15 @@
|
||||
global using System.Collections.Concurrent;
|
||||
global using System.Collections.ObjectModel;
|
||||
global using System.Collections.Specialized;
|
||||
global using System.ComponentModel;
|
||||
global using System.Diagnostics;
|
||||
global using System.Net;
|
||||
global using System.Net.NetworkInformation;
|
||||
global using System.Net.Sockets;
|
||||
global using System.Reactive;
|
||||
global using System.Reactive.Disposables;
|
||||
global using System.Reactive.Linq;
|
||||
global using ReactiveUI.Primitives;
|
||||
global using ReactiveUI.Primitives.Concurrency;
|
||||
global using ReactiveUI.Primitives.Disposables;
|
||||
global using ReactiveUI.Primitives.Signals;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Runtime.Versioning;
|
||||
@@ -16,10 +20,8 @@ global using System.Text.Json;
|
||||
global using System.Text.Json.Nodes;
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using DynamicData;
|
||||
global using DynamicData.Binding;
|
||||
global using ReactiveUI;
|
||||
global using ReactiveUI.Fody.Helpers;
|
||||
global using ReactiveUI.SourceGenerators;
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
global using ServiceLib.Enums;
|
||||
@@ -39,3 +41,5 @@ global using ServiceLib.Services;
|
||||
global using ServiceLib.Services.CoreConfig;
|
||||
global using ServiceLib.Services.Statistics;
|
||||
global using SQLite;
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ServiceLib.Handler.Builder;
|
||||
|
||||
public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult)
|
||||
@@ -95,6 +97,7 @@ public class CoreConfigContextBuilder
|
||||
context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.IsTunEnabled && context.AppConfig.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
var appConfig = JsonUtils.DeepCopy(config);
|
||||
@@ -322,14 +325,14 @@ public class CoreConfigContextBuilder
|
||||
{
|
||||
return await RegisterGroupNodeAsync(context, node);
|
||||
}
|
||||
return RegisterSingleNodeAsync(context, node);
|
||||
return await RegisterSingleNodeAsync(context, node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a single (non-group) node and, on success, adds it to the proxy map
|
||||
/// and records any domain addresses that should bypass the proxy.
|
||||
/// </summary>
|
||||
private static NodeValidatorResult RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||
private static async Task<NodeValidatorResult> RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node)
|
||||
{
|
||||
if (node.ConfigType.IsGroupType())
|
||||
{
|
||||
@@ -337,6 +340,29 @@ public class CoreConfigContextBuilder
|
||||
}
|
||||
|
||||
var nodeValidatorResult = NodeValidator.Validate(node, context.RunCoreType);
|
||||
|
||||
if (node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
var addressFileName = node.Address;
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
addressFileName = Utils.GetConfigPath(addressFileName);
|
||||
}
|
||||
if (!File.Exists(addressFileName))
|
||||
{
|
||||
nodeValidatorResult.Errors.Add(string.Format(ResUI.MsgCustomOutboundFileNotFound, node.Remarks, addressFileName));
|
||||
}
|
||||
try
|
||||
{
|
||||
var fileContent = await File.ReadAllTextAsync(addressFileName);
|
||||
context.CustomOutboundContent[node.IndexId] = fileContent;
|
||||
}
|
||||
catch
|
||||
{
|
||||
nodeValidatorResult.Errors.Add(string.Format(ResUI.MsgCustomOutboundFileNotFound, node.Remarks, addressFileName));
|
||||
}
|
||||
}
|
||||
|
||||
var msgs = new List<string>([.. nodeValidatorResult.Errors, .. nodeValidatorResult.Warnings]);
|
||||
if (msgs.Count > 0)
|
||||
{
|
||||
@@ -438,7 +464,7 @@ public class CoreConfigContextBuilder
|
||||
|
||||
if (!childNode.ConfigType.IsGroupType())
|
||||
{
|
||||
var childNodeResult = RegisterSingleNodeAsync(context, childNode);
|
||||
var childNodeResult = await RegisterSingleNodeAsync(context, childNode);
|
||||
childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w =>
|
||||
string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w)));
|
||||
childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e =>
|
||||
|
||||
@@ -36,6 +36,15 @@ public class NodeValidator
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
if (item.CoreType != coreType)
|
||||
{
|
||||
v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, coreType.ToString(), item.ConfigType));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ConfigType.IsGroupType())
|
||||
{
|
||||
// Group logic is handled in ValidateGroupNode
|
||||
|
||||
@@ -382,6 +382,13 @@ public static class ConfigHandler
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (profileItem.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
profileItem.Address = Utils.GetConfigPath(profileItem.Address);
|
||||
if (await AddCustomOutboundServer(config, profileItem, false) == 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddServerCommon(config, profileItem, true);
|
||||
@@ -579,6 +586,44 @@ public static class ConfigHandler
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public static async Task<int> AddCustomOutboundServer(Config config, ProfileItem profileItem, bool blDelete, bool toFile = true)
|
||||
{
|
||||
var fileName = profileItem.Address;
|
||||
if (!File.Exists(fileName))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var ext = Path.GetExtension(fileName);
|
||||
var newFileName = $"{Utils.GetGuid()}{ext}";
|
||||
//newFileName = Path.Combine(Utile.GetTempPath(), newFileName);
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(fileName, Utils.GetConfigPath(newFileName));
|
||||
if (blDelete)
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
profileItem.Address = newFileName;
|
||||
profileItem.ConfigType = EConfigType.Outbound;
|
||||
if (profileItem.Remarks.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.Remarks = $"import custom outbound@{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}";
|
||||
}
|
||||
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit an existing custom server configuration
|
||||
/// Updates the server's properties without changing the file
|
||||
@@ -600,6 +645,8 @@ public static class ConfigHandler
|
||||
item.CoreType = profileItem.CoreType;
|
||||
item.DisplayLog = profileItem.DisplayLog;
|
||||
item.PreSocksPort = profileItem.PreSocksPort;
|
||||
|
||||
item.ProtoExtra = profileItem.ProtoExtra;
|
||||
}
|
||||
|
||||
if (await SQLiteHelper.Instance.UpdateAsync(item) > 0)
|
||||
@@ -1479,7 +1526,7 @@ public static class ConfigHandler
|
||||
var matchedChildProfiles = childProfiles?.Where(p =>
|
||||
p != null &&
|
||||
p.IsValid() &&
|
||||
!p.ConfigType.IsComplexType() &&
|
||||
(!p.ConfigType.IsComplexType() || p.ConfigType == EConfigType.Outbound) &&
|
||||
(extraItem.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extraItem.Filter))
|
||||
)
|
||||
.ToList() ?? [];
|
||||
@@ -1675,68 +1722,179 @@ public static class ConfigHandler
|
||||
}
|
||||
|
||||
var subItem = await AppManager.Instance.GetSubItem(subid);
|
||||
|
||||
if (subItem?.CustomCoreType is null)
|
||||
{
|
||||
return await AddBatchServersDefaultCustom(config, strData, subid, isSub, subItem);
|
||||
}
|
||||
|
||||
return await AddBatchServersSpecificCustom(config, strData, subid, isSub, subItem);
|
||||
}
|
||||
|
||||
private static async Task<int> AddBatchServersDefaultCustom(
|
||||
Config config,
|
||||
string strData,
|
||||
string subid,
|
||||
bool isSub,
|
||||
SubItem? subItem)
|
||||
{
|
||||
var subRemarks = subItem?.Remarks;
|
||||
var preSocksPort = subItem?.PreSocksPort;
|
||||
|
||||
List<ProfileItem>? lstProfiles = null;
|
||||
//Is sing-box array configuration
|
||||
if (lstProfiles is null || lstProfiles.Count <= 0)
|
||||
// Safe Mode: Only allow full configuration if it's not from a subscription
|
||||
var lstProfiles = V2rayFmt.ResolveToCustomOutbound(strData, subRemarks);
|
||||
if (lstProfiles.Count == 0)
|
||||
{
|
||||
lstProfiles = SingboxFmt.ResolveFullArray(strData, subRemarks);
|
||||
lstProfiles = SingboxFmt.ResolveToCustomOutbound(strData, subRemarks);
|
||||
}
|
||||
//Is v2ray array configuration
|
||||
if (lstProfiles is null || lstProfiles.Count <= 0)
|
||||
{
|
||||
lstProfiles = V2rayFmt.ResolveFullArray(strData, subRemarks);
|
||||
}
|
||||
if (lstProfiles is { Count: > 0 })
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var it in lstProfiles)
|
||||
{
|
||||
it.Subid = subid;
|
||||
it.IsSub = isSub;
|
||||
it.PreSocksPort = preSocksPort;
|
||||
if (await AddCustomServer(config, it, true) == 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
ProfileItem? profileItem = null;
|
||||
//Is sing-box configuration
|
||||
profileItem ??= SingboxFmt.ResolveFull(strData, subRemarks);
|
||||
//Is v2ray configuration
|
||||
profileItem ??= V2rayFmt.ResolveFull(strData, subRemarks);
|
||||
//Is Html Page
|
||||
if (profileItem is null && HtmlPageFmt.IsHtmlPage(strData))
|
||||
if (lstProfiles.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
//Is Clash configuration
|
||||
profileItem ??= ClashFmt.ResolveFull(strData, subRemarks);
|
||||
//Is hysteria configuration
|
||||
profileItem ??= Hysteria2Fmt.ResolveFull2(strData, subRemarks);
|
||||
if (profileItem is null || profileItem.Address.IsNullOrEmpty())
|
||||
|
||||
var count = await AddCustomOutboundServers(config, lstProfiles, subid, isSub);
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
if (HtmlPageFmt.IsHtmlPage(strData))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var profileItem = ClashFmt.ResolveFull(strData, subRemarks)
|
||||
?? Hysteria2Fmt.ResolveFull2(strData, subRemarks);
|
||||
|
||||
if (profileItem == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
profileItem.Subid = subid;
|
||||
profileItem.IsSub = isSub;
|
||||
profileItem.PreSocksPort = preSocksPort;
|
||||
if (await AddCustomServer(config, profileItem, true) == 0)
|
||||
profileItem.PreSocksPort = subItem?.PreSocksPort;
|
||||
|
||||
return await AddCustomServer(config, profileItem, true) == 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
private static async Task<int> AddBatchServersSpecificCustom(
|
||||
Config config,
|
||||
string strData,
|
||||
string subid,
|
||||
bool isSub,
|
||||
SubItem subItem)
|
||||
{
|
||||
var subRemarks = subItem.Remarks;
|
||||
var customCoreType = subItem.CustomCoreType!.Value;
|
||||
|
||||
List<ProfileItem>? lstProfiles = customCoreType switch
|
||||
{
|
||||
return 1;
|
||||
ECoreType.Xray => V2rayFmt.ResolveToCustom(strData, subRemarks),
|
||||
ECoreType.sing_box => SingboxFmt.ResolveToCustom(strData, subRemarks),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (lstProfiles is not null)
|
||||
{
|
||||
if (lstProfiles.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var count = await AddCustomOutboundServers(config, lstProfiles, subid, isSub);
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return await SaveCustomRawFileServer(config, strData, subid, isSub, subItem, customCoreType);
|
||||
}
|
||||
|
||||
private static async Task<int> AddCustomOutboundServers(
|
||||
Config config,
|
||||
List<ProfileItem> lstProfiles,
|
||||
string subid,
|
||||
bool isSub)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var it in lstProfiles)
|
||||
{
|
||||
return -1;
|
||||
it.Subid = subid;
|
||||
it.IsSub = isSub;
|
||||
if (await AddCustomOutboundServer(config, it, true) == 0)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static async Task<int> SaveCustomRawFileServer(
|
||||
Config config,
|
||||
string strData,
|
||||
string subid,
|
||||
bool isSub,
|
||||
SubItem subItem,
|
||||
ECoreType customCoreType)
|
||||
{
|
||||
var ext = DetectFileExtension(strData);
|
||||
var fileName = Utils.GetTempPath($"{Utils.GetGuid(false)}{ext}");
|
||||
await File.WriteAllTextAsync(fileName, strData);
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = customCoreType,
|
||||
ConfigType = EConfigType.Custom,
|
||||
Address = fileName,
|
||||
Remarks = subItem.Remarks ?? customCoreType.ToString(),
|
||||
Subid = subid,
|
||||
IsSub = isSub,
|
||||
PreSocksPort = subItem.PreSocksPort,
|
||||
};
|
||||
|
||||
return await AddCustomServer(config, profileItem, true) == 0 ? 1 : -1;
|
||||
|
||||
static string DetectFileExtension(string data)
|
||||
{
|
||||
var trimmed = data.AsSpan().TrimStart();
|
||||
if (trimmed.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (trimmed[0] is '{' or '[')
|
||||
{
|
||||
return ".json";
|
||||
}
|
||||
|
||||
if (trimmed.StartsWith("---"))
|
||||
{
|
||||
return ".yaml";
|
||||
}
|
||||
|
||||
foreach (var line in trimmed.EnumerateLines())
|
||||
{
|
||||
var lineTrimmed = line.TrimStart();
|
||||
if (lineTrimmed.IsEmpty || lineTrimmed.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var colonIndex = lineTrimmed.IndexOf(':');
|
||||
if (colonIndex > 0)
|
||||
{
|
||||
var keySpan = lineTrimmed[..colonIndex];
|
||||
if (!keySpan.Contains(' ') && !keySpan.Contains('\t'))
|
||||
{
|
||||
if (colonIndex == lineTrimmed.Length - 1 || lineTrimmed[colonIndex + 1] is ' ' or '\t' or '\r' or '\n')
|
||||
{
|
||||
return ".yaml";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1837,6 +1995,7 @@ public static class ConfigHandler
|
||||
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
|
||||
EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
|
||||
EConfigType.PolicyGroup or EConfigType.ProxyChain => await AddServerCommon(config, profileItem, false),
|
||||
EConfigType.Outbound => await AddCustomOutboundServer(config, profileItem, true, false),
|
||||
_ => -1,
|
||||
};
|
||||
if (addStatus == 0)
|
||||
@@ -2038,6 +2197,7 @@ public static class ConfigHandler
|
||||
item.NextProfile = subItem.NextProfile;
|
||||
item.PreSocksPort = subItem.PreSocksPort;
|
||||
item.Memo = subItem.Memo;
|
||||
item.CustomCoreType = subItem.CustomCoreType;
|
||||
}
|
||||
|
||||
if (item.Id.IsNullOrEmpty())
|
||||
@@ -2078,7 +2238,7 @@ public static class ConfigHandler
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var customProfile = await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid && t.ConfigType == EConfigType.Custom).ToListAsync();
|
||||
var customProfile = await SQLiteHelper.Instance.TableAsync<ProfileItem>().Where(t => t.Subid == subid && (t.ConfigType == EConfigType.Custom || t.ConfigType == EConfigType.Outbound)).ToListAsync();
|
||||
if (isSub)
|
||||
{
|
||||
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileItem where isSub = 1 and subid = '{subid}'");
|
||||
|
||||
@@ -25,6 +25,11 @@ public class AnytlsFmt : BaseFmt
|
||||
var query = Utils.ParseQueryString(parsedUrl.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
if (GetQueryValue(query, "insecure") == "1")
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -41,6 +46,10 @@ public class AnytlsFmt : BaseFmt
|
||||
}
|
||||
var pw = item.Password;
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
dicQuery.Add("insecure", "1");
|
||||
}
|
||||
ToUriQuery(item, Global.None, ref dicQuery);
|
||||
|
||||
return ToUri(EConfigType.Anytls, item.Address, item.Port, pw, dicQuery, remark);
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class BaseFmt
|
||||
{
|
||||
private static readonly string[] _allowInsecureArray = new[] { "insecure", "allowInsecure", "allow_insecure" };
|
||||
|
||||
private static string UrlEncodeSafe(string? value) => Utils.UrlEncode(value ?? string.Empty);
|
||||
|
||||
protected static string GetIpv6(string address)
|
||||
@@ -67,7 +65,6 @@ public class BaseFmt
|
||||
{
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||
}
|
||||
if (item.EchConfigList.IsNotEmpty())
|
||||
{
|
||||
@@ -196,25 +193,6 @@ public class BaseFmt
|
||||
dicQuery.Add("alpn", Utils.UrlEncode(item.Alpn));
|
||||
}
|
||||
|
||||
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int ToUriQueryAllowInsecure(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
// Add two for compatibility
|
||||
dicQuery.Add("insecure", "1");
|
||||
dicQuery.Add("allowInsecure", "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
dicQuery.Add("insecure", "0");
|
||||
dicQuery.Add("allowInsecure", "0");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -252,19 +230,6 @@ public class BaseFmt
|
||||
item.Finalmask = string.Empty;
|
||||
}
|
||||
|
||||
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
else if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "0"))
|
||||
{
|
||||
item.AllowInsecure = Global.StringFalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.AllowInsecure = string.Empty;
|
||||
}
|
||||
|
||||
var net = GetQueryValue(query, "type", nameof(ETransport.raw));
|
||||
if (net == Global.RawNetworkAlias)
|
||||
{
|
||||
|
||||
@@ -162,6 +162,10 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
private static void ResolveHy2UriQuery(NameValueCollection query, ref ProfileItem item)
|
||||
{
|
||||
if (GetQueryValue(query, "insecure") == "1")
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
if (item.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
item.CertSha = GetQueryDecoded(query, "pinSHA256");
|
||||
@@ -198,6 +202,10 @@ public class Hysteria2Fmt : BaseFmt
|
||||
|
||||
private static void ToHy2UriQuery(ProfileItem item, ref Dictionary<string, string> dicQuery)
|
||||
{
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
dicQuery.Add("insecure", "1");
|
||||
}
|
||||
if (!item.CertSha.IsNullOrEmpty()
|
||||
&& !item.CertSha.Contains(','))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class InnerFmt
|
||||
public class InnerFmt : BaseFmt
|
||||
{
|
||||
private static readonly Lazy<string> SessionSalt = new(() => Utils.GetGuid(false));
|
||||
|
||||
@@ -50,19 +50,19 @@ public class InnerFmt
|
||||
var protocolExtra = item.GetProtocolExtra();
|
||||
// Only allow "self" as a special value for SubChildItems to avoid possible sources of attacks,
|
||||
// which means it will be replaced with the subid, otherwise set it to null
|
||||
//if (!protocolExtra.SubChildItems.IsNullOrEmpty())
|
||||
// if (!protocolExtra.SubChildItems.IsNullOrEmpty())
|
||||
if (protocolExtra.SubChildItems == "self")
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
SubChildItems = subid
|
||||
SubChildItems = subid,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
SubChildItems = null
|
||||
SubChildItems = null,
|
||||
};
|
||||
}
|
||||
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
||||
@@ -73,14 +73,14 @@ public class InnerFmt
|
||||
.ToList();
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
ChildItems = Utils.List2String(newChildIndexIds)
|
||||
ChildItems = Utils.List2String(newChildIndexIds),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
ChildItems = null
|
||||
ChildItems = null,
|
||||
};
|
||||
}
|
||||
item.SetProtocolExtra(protocolExtra);
|
||||
@@ -120,7 +120,7 @@ public class InnerFmt
|
||||
{
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
SubChildItems = "self"
|
||||
SubChildItems = "self",
|
||||
};
|
||||
}
|
||||
if (Utils.String2List(protocolExtra.ChildItems) is { Count: > 0 } childIndexIds)
|
||||
@@ -131,7 +131,7 @@ public class InnerFmt
|
||||
.ToList();
|
||||
protocolExtra = protocolExtra with
|
||||
{
|
||||
ChildItems = Utils.List2String(newChildIndexIds)
|
||||
ChildItems = Utils.List2String(newChildIndexIds),
|
||||
};
|
||||
}
|
||||
itemClone.SetProtocolExtra(protocolExtra);
|
||||
@@ -175,6 +175,19 @@ public class InnerFmt
|
||||
jsonObj["TransportExtra"] = JsonUtils.Serialize(transportExtraObj, false);
|
||||
jsonObj.Remove("TransportExtraObj");
|
||||
}
|
||||
var customOutboundFilePath = string.Empty;
|
||||
if (jsonObj.TryGetPropertyValue("CustomOutboundObj", out var customOutboundNode)
|
||||
&& customOutboundNode is JsonObject customOutboundObj)
|
||||
{
|
||||
var customOutboundContent = JsonUtils.Serialize(customOutboundObj, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
});
|
||||
customOutboundFilePath = WriteAllText(customOutboundContent);
|
||||
jsonObj.Remove("CustomOutboundObj");
|
||||
}
|
||||
var profileItem = JsonUtils.Deserialize<ProfileItem>(JsonUtils.Serialize(jsonObj, false));
|
||||
if (profileItem is null)
|
||||
{
|
||||
@@ -193,6 +206,14 @@ public class InnerFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (profileItem.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
if (customOutboundFilePath.IsNullOrEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
profileItem.Address = customOutboundFilePath;
|
||||
}
|
||||
var protocolExtra = profileItem.GetProtocolExtra();
|
||||
var multipleLoad = protocolExtra.MultipleLoad;
|
||||
if (multipleLoad is not null && !Enum.IsDefined(typeof(EMultipleLoad), multipleLoad))
|
||||
@@ -209,6 +230,26 @@ public class InnerFmt
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (item.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
var customOutboundFilePath = item.Address;
|
||||
if (!File.Exists(customOutboundFilePath))
|
||||
{
|
||||
customOutboundFilePath = Utils.GetConfigPath(customOutboundFilePath);
|
||||
}
|
||||
if (!File.Exists(customOutboundFilePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!customOutboundFilePath.IsNullOrEmpty()
|
||||
&& File.Exists(customOutboundFilePath)
|
||||
&& File.ReadAllText(customOutboundFilePath) is { Length: > 0 } customOutboundContent
|
||||
&& JsonUtils.ParseJson(customOutboundContent) is JsonObject customOutboundObj)
|
||||
{
|
||||
jsonObj["CustomOutboundObj"] = customOutboundObj;
|
||||
jsonObj.Remove("Address");
|
||||
}
|
||||
}
|
||||
// unflatten
|
||||
// move jsonObj.ProtoExtra (string) to jsonObj.ProtoExtraObj
|
||||
// move jsonObj.TransportExtra (string) to jsonObj.TransportExtraObj
|
||||
@@ -296,7 +337,7 @@ public class InnerFmt
|
||||
JsonValue value when value.TryGetValue<string>(out var str) => string.IsNullOrEmpty(str),
|
||||
JsonObject obj => obj.Count == 0,
|
||||
JsonArray arr => arr.Count == 0,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,102 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class SingboxFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
public static List<ProfileItem> ResolveToCustom(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||
if (configObjects is not { Length: > 0 })
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, false);
|
||||
}
|
||||
|
||||
public static List<ProfileItem> ResolveToCustomOutbound(string strData, string? subRemarks)
|
||||
{
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, true);
|
||||
}
|
||||
|
||||
private static List<ProfileItem> ResolveCommon(JsonNode? jsonNode, string? subRemarks, bool isOutbound)
|
||||
{
|
||||
if (jsonNode is JsonArray jsonArray)
|
||||
{
|
||||
return
|
||||
[
|
||||
.. jsonArray.Select(item => ResolveCommon(item, subRemarks, isOutbound))
|
||||
.Where(list => list is { Count: > 0 })
|
||||
.SelectMany(list => list),
|
||||
];
|
||||
}
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
// Process the individual JSON object
|
||||
var profileList = new List<ProfileItem>();
|
||||
if (!isOutbound)
|
||||
{
|
||||
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
||||
profileList.Add(fullProfile);
|
||||
if (fullProfile is not null)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
}
|
||||
profileList.AddRange(ResolveFullToOutbound(jsonObject, subRemarks));
|
||||
if (profileList.Count != 0)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
var outboundProfile = ResolveOutbound(jsonObject, subRemarks);
|
||||
if (outboundProfile is not null)
|
||||
{
|
||||
profileList.Add(outboundProfile);
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveFull(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject?["inbounds"] == null
|
||||
|| jsonObject["outbounds"] == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
if (jsonObject["outbounds"] is JsonArray outboundsArray)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var profileIt = ResolveFull(objectString, subRemarks);
|
||||
if (!outboundsArray.Any(IsValidSingboxOutbound))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = ECoreType.sing_box,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "singbox_custom",
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
private static List<ProfileItem> ResolveFullToOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject?["outbounds"] is not JsonArray outboundsArray)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var outbound in outboundsArray)
|
||||
{
|
||||
if (outbound is not JsonObject outboundObj)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var profileIt = ResolveOutbound(outboundObj, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
@@ -23,25 +106,58 @@ public class SingboxFmt : BaseFmt
|
||||
return lstResult;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
private static ProfileItem? ResolveOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
var config = JsonUtils.ParseJson(strData);
|
||||
if (config?["inbounds"] == null
|
||||
|| config["outbounds"] == null
|
||||
|| config["route"] == null
|
||||
|| config["dns"] == null)
|
||||
if (!IsValidSingboxOutbound(jsonObject))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(strData);
|
||||
var type = jsonObject["type"]?.ToString();
|
||||
if (type is null or "direct" or "block" or "dns" or "selector" or "urltest")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var tag = jsonObject["tag"]?.ToString();
|
||||
var remarks = $"{type}_{tag}";
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.Outbound,
|
||||
CoreType = ECoreType.sing_box,
|
||||
Address = fileName,
|
||||
Remarks = subRemarks ?? "singbox_custom"
|
||||
Remarks = remarks,
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
private static bool IsValidSingboxOutbound(JsonNode? jsonNode)
|
||||
{
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var matchedCounter = 0;
|
||||
if (string.IsNullOrEmpty(jsonObject["type"]?.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
matchedCounter += 1;
|
||||
if (!string.IsNullOrEmpty(jsonObject["tag"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["server"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["server_port"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["tls"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
return matchedCounter >= 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class TrojanFmt : BaseFmt
|
||||
{
|
||||
private static readonly List<string> _insecureQueryKeys = new() { "allowInsecure", "insecure" };
|
||||
|
||||
public static ProfileItem? Resolve(string str, out string msg)
|
||||
{
|
||||
msg = ResUI.ConfigurationFormatIncorrect;
|
||||
@@ -23,6 +25,10 @@ public class TrojanFmt : BaseFmt
|
||||
item.Password = Utils.UrlDecode(url.UserInfo);
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
if (_insecureQueryKeys.Any(q => GetQueryValue(query, q) == "1"))
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with { Flow = GetQueryValue(query, "flow") });
|
||||
ResolveUriQuery(query, ref item);
|
||||
|
||||
@@ -41,6 +47,10 @@ public class TrojanFmt : BaseFmt
|
||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||
}
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
_insecureQueryKeys.ForEach(q => dicQuery.Add(q, "1"));
|
||||
}
|
||||
if (!item.GetProtocolExtra().Flow.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("flow", item.GetProtocolExtra().Flow);
|
||||
|
||||
@@ -30,6 +30,10 @@ public class TuicFmt : BaseFmt
|
||||
|
||||
var query = Utils.ParseQueryString(url.Query);
|
||||
ResolveUriQuery(query, ref item);
|
||||
if (GetQueryValue(query, "allow_insecure") == "1")
|
||||
{
|
||||
item.AllowInsecure = Global.StringTrue;
|
||||
}
|
||||
item.SetProtocolExtra(item.GetProtocolExtra() with
|
||||
{
|
||||
CongestionControl = GetQueryValue(query, "congestion_control")
|
||||
@@ -53,7 +57,10 @@ public class TuicFmt : BaseFmt
|
||||
|
||||
var dicQuery = new Dictionary<string, string>();
|
||||
ToUriQueryLite(item, ref dicQuery);
|
||||
|
||||
if (item.GetAllowInsecure())
|
||||
{
|
||||
dicQuery.Add("allow_insecure", "1");
|
||||
}
|
||||
if (!item.GetProtocolExtra().CongestionControl.IsNullOrEmpty())
|
||||
{
|
||||
dicQuery.Add("congestion_control", item.GetProtocolExtra().CongestionControl);
|
||||
|
||||
@@ -2,47 +2,165 @@ namespace ServiceLib.Handler.Fmt;
|
||||
|
||||
public class V2rayFmt : BaseFmt
|
||||
{
|
||||
public static List<ProfileItem>? ResolveFullArray(string strData, string? subRemarks)
|
||||
public static List<ProfileItem> ResolveToCustom(string strData, string? subRemarks)
|
||||
{
|
||||
var configObjects = JsonUtils.Deserialize<object[]>(strData);
|
||||
if (configObjects is not { Length: > 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var configObject in configObjects)
|
||||
{
|
||||
var objectString = JsonUtils.Serialize(configObject);
|
||||
var profileIt = ResolveFull(objectString, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
|
||||
return lstResult;
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, false);
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||
public static List<ProfileItem> ResolveToCustomOutbound(string strData, string? subRemarks)
|
||||
{
|
||||
var config = JsonUtils.ParseJson(strData);
|
||||
if (config?["inbounds"] == null
|
||||
|| config["outbounds"] == null
|
||||
|| config["routing"] == null)
|
||||
var jsonNode = JsonUtils.ParseJson(strData);
|
||||
return ResolveCommon(jsonNode, subRemarks, true);
|
||||
}
|
||||
|
||||
private static List<ProfileItem> ResolveCommon(JsonNode? jsonNode, string? subRemarks, bool isOutbound)
|
||||
{
|
||||
if (jsonNode is JsonArray jsonArray)
|
||||
{
|
||||
return
|
||||
[
|
||||
.. jsonArray.Select(item => ResolveCommon(item, subRemarks, isOutbound))
|
||||
.Where(list => list is { Count: > 0 })
|
||||
.SelectMany(list => list),
|
||||
];
|
||||
}
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
// Process the individual JSON object
|
||||
var profileList = new List<ProfileItem>();
|
||||
if (!isOutbound)
|
||||
{
|
||||
var fullProfile = ResolveFull(jsonObject, subRemarks);
|
||||
profileList.Add(fullProfile);
|
||||
if (fullProfile is not null)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
}
|
||||
profileList.AddRange(ResolveFullToOutbound(jsonObject, subRemarks));
|
||||
if (profileList.Count != 0)
|
||||
{
|
||||
return profileList;
|
||||
}
|
||||
var outboundProfile = ResolveOutbound(jsonObject, subRemarks);
|
||||
if (outboundProfile is not null)
|
||||
{
|
||||
profileList.Add(outboundProfile);
|
||||
}
|
||||
return profileList;
|
||||
}
|
||||
|
||||
private static ProfileItem? ResolveFull(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject?["inbounds"] == null
|
||||
|| jsonObject["outbounds"] == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(strData);
|
||||
if (jsonObject["outbounds"] is JsonArray outboundsArray)
|
||||
{
|
||||
if (!outboundsArray.Any(IsValidV2rayOutbound))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = config?["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom"
|
||||
Remarks = jsonObject["remarks"]?.ToString() ?? subRemarks ?? "v2ray_custom",
|
||||
};
|
||||
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
public static List<ProfileItem> ResolveFullToOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (jsonObject["outbounds"] is not JsonArray outboundsArray)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
List<ProfileItem> lstResult = [];
|
||||
foreach (var outbound in outboundsArray)
|
||||
{
|
||||
if (outbound is not JsonObject outboundObj)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var profileIt = ResolveOutbound(outboundObj, subRemarks);
|
||||
if (profileIt != null)
|
||||
{
|
||||
lstResult.Add(profileIt);
|
||||
}
|
||||
}
|
||||
return lstResult;
|
||||
}
|
||||
|
||||
public static ProfileItem? ResolveOutbound(JsonObject jsonObject, string? subRemarks)
|
||||
{
|
||||
if (!IsValidV2rayOutbound(jsonObject))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var protocol = jsonObject["protocol"]?.ToString();
|
||||
if (protocol is null or "freedom" or "blackhole" or "dns" or "loopback")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var tag = jsonObject["tag"]?.ToString();
|
||||
var remarks = $"{protocol}_{tag}";
|
||||
var fileName = WriteAllText(JsonUtils.Serialize(jsonObject));
|
||||
var profileItem = new ProfileItem
|
||||
{
|
||||
ConfigType = EConfigType.Outbound,
|
||||
CoreType = ECoreType.Xray,
|
||||
Address = fileName,
|
||||
Remarks = remarks,
|
||||
};
|
||||
return profileItem;
|
||||
}
|
||||
|
||||
private static bool IsValidV2rayOutbound(JsonNode? jsonNode)
|
||||
{
|
||||
if (jsonNode is not JsonObject jsonObject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var matchedCounter = 0;
|
||||
if (string.IsNullOrEmpty(jsonObject["protocol"]?.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
matchedCounter += 1;
|
||||
if (!string.IsNullOrEmpty(jsonObject["settings"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["streamSettings"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["tag"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(jsonObject["mux"]?.ToString()))
|
||||
{
|
||||
matchedCounter += 1;
|
||||
}
|
||||
|
||||
return matchedCounter >= 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ public class CoreAdminManager
|
||||
.ExecuteBufferedAsync();
|
||||
|
||||
await UpdateFunc(false, result.StandardOutput.ToString());
|
||||
|
||||
await Task.Delay(1000); // Wait for a second to ensure the process is killed
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -118,7 +118,7 @@ public class GroupProfileManager
|
||||
return childProfiles?.Where(p =>
|
||||
p != null &&
|
||||
p.IsValid() &&
|
||||
!p.ConfigType.IsComplexType() &&
|
||||
(!p.ConfigType.IsComplexType() || p.ConfigType == EConfigType.Outbound) &&
|
||||
(extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter))
|
||||
)
|
||||
.ToList() ?? [];
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//using System.Reactive.Linq;
|
||||
|
||||
namespace ServiceLib.Manager;
|
||||
|
||||
public class ProfileExManager
|
||||
|
||||
@@ -11,6 +11,8 @@ public record CoreConfigContext
|
||||
public Config AppConfig { get; init; } = new();
|
||||
public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new();
|
||||
|
||||
public Dictionary<string, string> CustomOutboundContent { get; init; } = new();
|
||||
|
||||
// Test ServerTestItem Map
|
||||
public Dictionary<string, string> ServerTestItemMap { get; init; } = new();
|
||||
|
||||
@@ -22,4 +24,7 @@ public record CoreConfigContext
|
||||
|
||||
public bool IsWindows { get; init; }
|
||||
public bool IsMacOS { get; init; }
|
||||
|
||||
// Generation Context
|
||||
public Dictionary<object, string> CustomOutboundMap { get; init; } = new();
|
||||
}
|
||||
|
||||
@@ -364,8 +364,6 @@ public class StreamSettings4Ray
|
||||
|
||||
public class TlsSettings4Ray
|
||||
{
|
||||
public bool? allowInsecure { get; set; }
|
||||
|
||||
public string? serverName { get; set; }
|
||||
|
||||
public List<string>? alpn { get; set; }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
public class CheckUpdateModel : ReactiveObject
|
||||
public partial class CheckUpdateModel : ReactiveObject
|
||||
{
|
||||
public bool? IsSelected { get; set; }
|
||||
public ECoreType? CoreType { get; set; }
|
||||
[Reactive] public string? Remarks { get; set; }
|
||||
[Reactive] public partial string? Remarks { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
public bool? IsFinished { get; set; }
|
||||
public bool IsGeoFile { get; set; }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
[Serializable]
|
||||
public class ClashProxyModel : ReactiveObject
|
||||
public partial class ClashProxyModel : ReactiveObject
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
@@ -9,9 +9,9 @@ public class ClashProxyModel : ReactiveObject
|
||||
|
||||
public string? Now { get; set; }
|
||||
|
||||
[Reactive] public int Delay { get; set; }
|
||||
[Reactive] public partial int Delay { get; set; }
|
||||
|
||||
[Reactive] public string? DelayName { get; set; }
|
||||
[Reactive] public partial string? DelayName { get; set; }
|
||||
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace ServiceLib.Models.Dto;
|
||||
|
||||
[Serializable]
|
||||
public class ProfileItemModel : ReactiveObject
|
||||
public partial class ProfileItemModel : ReactiveObject
|
||||
{
|
||||
public bool IsActive { get; set; }
|
||||
public string IndexId { get; set; }
|
||||
@@ -16,30 +16,30 @@ public class ProfileItemModel : ReactiveObject
|
||||
public int Sort { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int Delay { get; set; }
|
||||
public partial int Delay { get; set; }
|
||||
|
||||
public decimal Speed { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string DelayVal { get; set; }
|
||||
public partial string DelayVal { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SpeedVal { get; set; }
|
||||
public partial string SpeedVal { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string IpInfo { get; set; }
|
||||
public partial string IpInfo { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TodayUp { get; set; }
|
||||
public partial string TodayUp { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TodayDown { get; set; }
|
||||
public partial string TodayDown { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TotalUp { get; set; }
|
||||
public partial string TotalUp { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string TotalDown { get; set; }
|
||||
public partial string TotalDown { get; set; }
|
||||
|
||||
public string GetSummary()
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ public class ProfileItem
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
if (IsComplex())
|
||||
if (IsComplex() || ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,4 +51,7 @@ public record ProtocolExtraItem
|
||||
public string? SubChildItems { get; init; }
|
||||
public string? Filter { get; init; }
|
||||
public EMultipleLoad? MultipleLoad { get; init; }
|
||||
|
||||
// custom outbound
|
||||
public bool? IsSingboxEndpoint { get; init; }
|
||||
}
|
||||
|
||||
@@ -33,4 +33,6 @@ public class SubItem
|
||||
public int? PreSocksPort { get; set; }
|
||||
|
||||
public string? Memo { get; set; }
|
||||
|
||||
public ECoreType? CustomCoreType { get; set; }
|
||||
}
|
||||
|
||||
38
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
38
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -438,6 +438,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom config core 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string LvCustomCoreType {
|
||||
get {
|
||||
return ResourceManager.GetString("LvCustomCoreType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom icon 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -735,6 +744,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add a custom outbound 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAddCustomOutboundServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAddCustomOutboundServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add a custom configuration 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -1933,7 +1951,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Warning: Xray will disable allowInsecure (skip certificate verification) in August 2026. Please switch to pinnedPeerCertSha256 (fixed certificate fingerprint) as soon as possible. allowInsecure will not be usable after its expiration. 的本地化字符串。
|
||||
/// 查找类似 The current node uses an unencrypted connection, meaning your communications could be directly monitored by network intermediaries controlled by authoritarian governments. For security reasons, nodes of this type cannot connect via Xray-core versions 26.2.6 or higher. If this is a self-built node, please enable TLS or other secure encryption, or pin the certificate using pinSHA256. If this is an airport/provider node, please contact your service provider for a technical upgrade. If the provider refuses to c [字符串的其余部分被截断]"; 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgAllowInsecureDeprecated {
|
||||
get {
|
||||
@@ -1977,6 +1995,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom outbound {0} file not found: {1} 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgCustomOutboundFileNotFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgCustomOutboundFileNotFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2925,6 +2952,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Only single outbound/endpoint supported for xray/sing-box 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbCustomOutboundTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbCustomOutboundTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Direct Target Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -1848,4 +1848,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 Address</value>
|
||||
</data>
|
||||
<data name="menuAddCustomOutboundServer" xml:space="preserve">
|
||||
<value>Add a custom outbound</value>
|
||||
</data>
|
||||
<data name="MsgCustomOutboundFileNotFound" xml:space="preserve">
|
||||
<value>Custom outbound {0} file not found: {1}</value>
|
||||
</data>
|
||||
<data name="TbCustomOutboundTip" xml:space="preserve">
|
||||
<value>Only single outbound/endpoint supported for xray/sing-box</value>
|
||||
</data>
|
||||
<data name="LvCustomCoreType" xml:space="preserve">
|
||||
<value>Custom config core</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1849,4 +1849,16 @@
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 地址</value>
|
||||
</data>
|
||||
<data name="menuAddCustomOutboundServer" xml:space="preserve">
|
||||
<value>添加自定义出站</value>
|
||||
</data>
|
||||
<data name="MsgCustomOutboundFileNotFound" xml:space="preserve">
|
||||
<value>自定义出站 {0} 的文件未找到:{1}</value>
|
||||
</data>
|
||||
<data name="TbCustomOutboundTip" xml:space="preserve">
|
||||
<value>仅支持 xray/sing-box 的单个 outbound/endpoin</value>
|
||||
</data>
|
||||
<data name="LvCustomCoreType" xml:space="preserve">
|
||||
<value>自定义配置核心</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -66,6 +66,11 @@ kill_children "$PID"
|
||||
echo "Terminating main process: $PID"
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
|
||||
# Wait a little for process/port resources to be fully released
|
||||
FINAL_WAIT_SECONDS=1
|
||||
echo "Waiting ${FINAL_WAIT_SECONDS}s for resources to settle..."
|
||||
sleep "$FINAL_WAIT_SECONDS"
|
||||
|
||||
echo "============================================"
|
||||
echo "Process $PID and all its children have been terminated"
|
||||
echo "============================================"
|
||||
|
||||
@@ -63,6 +63,11 @@ kill_descendants "$PID"
|
||||
echo "Terminating main process: $PID"
|
||||
kill -9 "$PID" 2>/dev/null || true
|
||||
|
||||
# Wait a little for process/port resources to be fully released
|
||||
FINAL_WAIT_SECONDS=1
|
||||
echo "Waiting ${FINAL_WAIT_SECONDS}s for resources to settle..."
|
||||
sleep "$FINAL_WAIT_SECONDS"
|
||||
|
||||
echo "============================================"
|
||||
echo "Process $PID and all its descendants have been terminated"
|
||||
echo "============================================"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
[
|
||||
{
|
||||
"network": "udp",
|
||||
"network": [
|
||||
"udp"
|
||||
],
|
||||
"port": [
|
||||
135,
|
||||
137,
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
<PackageReference Include="ReactiveUI">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.Fody" />
|
||||
<PackageReference Include="ReactiveUI.SourceGenerators">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="sqlite-net-e" />
|
||||
<PackageReference Include="Repobot.SQLite.Unofficial" />
|
||||
<PackageReference Include="NLog" />
|
||||
|
||||
@@ -57,13 +57,10 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
|
||||
ConvertGeo2Ruleset();
|
||||
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
|
||||
ret.Data = ApplyFullConfigTemplate();
|
||||
ret.Data = ApplyFinalConfigModifiers();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -107,7 +104,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
|
||||
if (!(Global.SingboxSupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType() || it.ConfigType is EConfigType.Outbound))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -174,7 +171,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -236,7 +233,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,54 +2,137 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigSingboxService
|
||||
{
|
||||
private string ApplyFullConfigTemplate()
|
||||
private string ApplyFinalConfigModifiers()
|
||||
{
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
var coreConfigContent = ApplyCustomOutboundReplace();
|
||||
|
||||
return ApplyFullConfigTemplate(coreConfigContent);
|
||||
}
|
||||
|
||||
private string ApplyCustomOutboundReplace()
|
||||
{
|
||||
var coreConfigContent = JsonUtils.Serialize(_coreConfig);
|
||||
if (context.CustomOutboundMap.Count == 0)
|
||||
{
|
||||
return coreConfigContent;
|
||||
}
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent) as JsonObject;
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
ReplaceCustomOutbounds(_coreConfig.outbounds, coreConfigOutboundsNode);
|
||||
coreConfigNode!["outbounds"] = coreConfigOutboundsNode;
|
||||
var coreConfigEndpointsNode = coreConfigNode?["endpoints"] as JsonArray ?? [];
|
||||
ReplaceCustomOutbounds(_coreConfig.endpoints, coreConfigEndpointsNode);
|
||||
if (coreConfigEndpointsNode.Count > 0)
|
||||
{
|
||||
coreConfigNode!["endpoints"] = coreConfigEndpointsNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
coreConfigNode?.Remove("endpoints");
|
||||
}
|
||||
return JsonUtils.Serialize(coreConfigNode);
|
||||
|
||||
void ReplaceCustomOutbounds(IReadOnlyList<BaseServer4Sbox>? source, JsonArray jsonArrayOutbounds)
|
||||
{
|
||||
foreach (var outbound in source ?? [])
|
||||
{
|
||||
if (!context.CustomOutboundMap.TryGetValue(outbound, out var customOutboundIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var outboundTag = outbound.tag;
|
||||
var outboundDetour = outbound.detour ?? string.Empty;
|
||||
var outboundBindInterface = outbound.bind_interface ?? string.Empty;
|
||||
var customOutboundContent = context.CustomOutboundContent[customOutboundIndex];
|
||||
var containTagPlaceholder = customOutboundContent.Contains("{{tag}}");
|
||||
var containDetourPlaceholder = customOutboundContent.Contains("{{detour}}");
|
||||
var containBindInterfacePlaceholder = customOutboundContent.Contains("{{interface}}");
|
||||
customOutboundContent = customOutboundContent.Replace("{{tag}}", outboundTag);
|
||||
customOutboundContent = customOutboundContent.Replace("{{detour}}", outboundDetour);
|
||||
customOutboundContent = customOutboundContent.Replace("{{interface}}", outboundBindInterface);
|
||||
var customOutboundObj = JsonUtils.ParseJson(customOutboundContent) as JsonObject;
|
||||
|
||||
if (!containTagPlaceholder)
|
||||
{
|
||||
customOutboundObj?["tag"] = outboundTag;
|
||||
}
|
||||
if (!containDetourPlaceholder && !outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj?["detour"] = outboundDetour;
|
||||
}
|
||||
else if (outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj?.Remove("detour");
|
||||
}
|
||||
if (!containBindInterfacePlaceholder && !outboundBindInterface.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj?["bind_interface"] = outboundBindInterface;
|
||||
}
|
||||
|
||||
var index = jsonArrayOutbounds
|
||||
.Select((node, idx) => new { node, idx })
|
||||
.FirstOrDefault(x => x.node?["tag"]?.ToString() == outboundTag)?.idx ?? -1;
|
||||
if (index != -1)
|
||||
{
|
||||
jsonArrayOutbounds[index] = customOutboundObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ApplyFullConfigTemplate(string coreConfigContent)
|
||||
{
|
||||
var fullConfigTemplate = context.FullConfigTemplate;
|
||||
if (fullConfigTemplate is not { Enabled: true })
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
// Process outbounds
|
||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] as JsonArray ?? [];
|
||||
foreach (var outbound in _coreConfig.outbounds)
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent);
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
foreach (var outbound in coreConfigOutboundsNode)
|
||||
{
|
||||
if (outbound.type.ToLower() is "direct" or "block")
|
||||
if (outbound["type"]?.ToString()?.ToLower() is "direct" or "block")
|
||||
{
|
||||
if (fullConfigTemplate.AddProxyOnly == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (outbound.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound.server ?? string.Empty))
|
||||
if (outbound["detour"] is null && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty() && !Utils.IsPrivateNetwork(outbound["server"]?.ToString() ?? string.Empty))
|
||||
{
|
||||
outbound.detour = fullConfigTemplate.ProxyDetour;
|
||||
outbound["detour"] = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||
}
|
||||
fullConfigTemplateNode["outbounds"] = customOutboundsNode;
|
||||
|
||||
// Process endpoints
|
||||
if (_coreConfig.endpoints is { Count: > 0 })
|
||||
if (fullConfigTemplateNode["endpoints"] is JsonArray { Count: > 0 } coreConfigEndpointsNode)
|
||||
{
|
||||
var customEndpointsNode = fullConfigTemplateNode["endpoints"] as JsonArray ?? [];
|
||||
foreach (var endpoint in _coreConfig.endpoints)
|
||||
foreach (var endpoint in coreConfigEndpointsNode)
|
||||
{
|
||||
if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
if (endpoint["detour"] is null && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty())
|
||||
{
|
||||
endpoint.detour = fullConfigTemplate.ProxyDetour;
|
||||
endpoint["detour"] = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
customEndpointsNode.Add(JsonUtils.DeepCopy(endpoint));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public partial class CoreConfigSingboxService
|
||||
private List<BaseServer4Sbox> BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag, bool withSelector = true)
|
||||
{
|
||||
var proxyOutboundList = new List<BaseServer4Sbox>();
|
||||
if (!_node.ConfigType.IsComplexType())
|
||||
if (!_node.ConfigType.IsGroupType())
|
||||
{
|
||||
var outbound = BuildProxyOutbound(baseTagName);
|
||||
proxyOutboundList.Add(outbound);
|
||||
@@ -35,6 +35,10 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
var outbound = BuildProxyServer();
|
||||
outbound.tag = baseTagName;
|
||||
if (_node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
context.CustomOutboundMap[outbound] = _node.IndexId;
|
||||
}
|
||||
return outbound;
|
||||
}
|
||||
|
||||
@@ -59,6 +63,20 @@ public partial class CoreConfigSingboxService
|
||||
try
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound);
|
||||
if (_node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
if (_node.GetProtocolExtra().IsSingboxEndpoint == true)
|
||||
{
|
||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||
return endpoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
var outbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||
return outbound;
|
||||
}
|
||||
}
|
||||
|
||||
if (_node.ConfigType == EConfigType.WireGuard)
|
||||
{
|
||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||
@@ -593,7 +611,7 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
type = "selector",
|
||||
tag = baseTagName,
|
||||
outbounds = JsonUtils.DeepCopy(proxyTags),
|
||||
outbounds = [.. proxyTags],
|
||||
interrupt_exist_connections = false,
|
||||
};
|
||||
outSelector.outbounds.Insert(0, outUrltest.tag);
|
||||
@@ -721,9 +739,9 @@ public partial class CoreConfigSingboxService
|
||||
return resultOutbounds;
|
||||
}
|
||||
|
||||
private static List<BaseServer4Sbox> CloneOutbounds(List<BaseServer4Sbox> source)
|
||||
private List<BaseServer4Sbox> CloneOutbounds(List<BaseServer4Sbox> source)
|
||||
{
|
||||
if (source is null || source.Count == 0)
|
||||
if (source is not { Count: > 0 })
|
||||
{
|
||||
return [];
|
||||
}
|
||||
@@ -740,9 +758,14 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
clone = JsonUtils.DeepCopy(endpoint);
|
||||
}
|
||||
if (clone is not null)
|
||||
if (clone is null)
|
||||
{
|
||||
result.Add(clone);
|
||||
continue;
|
||||
}
|
||||
result.Add(clone);
|
||||
if (context.CustomOutboundMap.ContainsKey(item))
|
||||
{
|
||||
context.CustomOutboundMap[clone] = context.CustomOutboundMap[item];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -43,6 +43,22 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
// Traffic addressed to the TUN interface's own addresses must never reach an
|
||||
// outbound. auto_route hijacks the default route, so `direct` writes such a
|
||||
// packet straight back into the TUN, which hands it to the outbound again -
|
||||
// an infinite loop that pins a CPU core. Drop instead of rejecting so no
|
||||
// ICMP unreachable is generated back towards the same addresses.
|
||||
var tunAddresses = _coreConfig.inbounds.FirstOrDefault(i => i.type == "tun")?.address;
|
||||
if (tunAddresses?.Count > 0)
|
||||
{
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
ip_cidr = [.. tunAddresses],
|
||||
action = "reject",
|
||||
method = "drop",
|
||||
});
|
||||
}
|
||||
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
@@ -558,7 +574,8 @@ public partial class CoreConfigSingboxService
|
||||
|
||||
if (node == null
|
||||
|| (!Global.SingboxSupportConfigType.Contains(node.ConfigType)
|
||||
&& !node.ConfigType.IsGroupType()))
|
||||
&& !node.ConfigType.IsGroupType()
|
||||
&& node.ConfigType is not EConfigType.Outbound))
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
{
|
||||
ApplyFinalFragment();
|
||||
}
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
var finalRule = BuildFinalRule();
|
||||
if (!string.IsNullOrEmpty(finalRule?.balancerTag))
|
||||
@@ -75,7 +73,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = ApplyFullConfigTemplate();
|
||||
ret.Data = ApplyFinalConfigModifiers();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -119,7 +117,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
|
||||
foreach (var it in selecteds)
|
||||
{
|
||||
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType()))
|
||||
if (!(Global.XraySupportConfigType.Contains(it.ConfigType) || it.ConfigType.IsGroupType() || it.ConfigType is EConfigType.Outbound))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -216,7 +214,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
ApplyOutboundSendThrough();
|
||||
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -293,7 +291,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
|
||||
|
||||
ret.Msg = string.Format(ResUI.SuccessfulConfiguration, "");
|
||||
ret.Success = true;
|
||||
ret.Data = JsonUtils.Serialize(_coreConfig);
|
||||
ret.Data = ApplyCustomOutboundReplace();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -2,24 +2,105 @@ namespace ServiceLib.Services.CoreConfig;
|
||||
|
||||
public partial class CoreConfigV2rayService
|
||||
{
|
||||
private string ApplyFullConfigTemplate()
|
||||
private string ApplyFinalConfigModifiers()
|
||||
{
|
||||
ApplyOutboundBindInterface();
|
||||
ApplyOutboundSendThrough();
|
||||
|
||||
var coreConfigContent = ApplyCustomOutboundReplace();
|
||||
|
||||
return ApplyFullConfigTemplate(coreConfigContent);
|
||||
}
|
||||
|
||||
private string ApplyCustomOutboundReplace()
|
||||
{
|
||||
var coreConfigContent = JsonUtils.Serialize(_coreConfig);
|
||||
if (context.CustomOutboundMap.Count == 0)
|
||||
{
|
||||
return coreConfigContent;
|
||||
}
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent) as JsonObject;
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds ?? [])
|
||||
{
|
||||
if (!context.CustomOutboundMap.TryGetValue(outbound, out var customOutboundIndex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var outboundTag = outbound.tag;
|
||||
var outboundDetour = outbound.streamSettings?.sockopt?.dialerProxy ?? string.Empty;
|
||||
var outboundBindInterface = outbound.streamSettings?.sockopt?.Interface ?? string.Empty;
|
||||
var customOutboundContent = context.CustomOutboundContent[customOutboundIndex];
|
||||
var containTagPlaceholder = customOutboundContent.Contains("{{tag}}");
|
||||
var containDetourPlaceholder = customOutboundContent.Contains("{{detour}}");
|
||||
var containBindInterfacePlaceholder = customOutboundContent.Contains("{{interface}}");
|
||||
customOutboundContent = customOutboundContent.Replace("{{tag}}", outboundTag);
|
||||
customOutboundContent = customOutboundContent.Replace("{{detour}}", outboundDetour);
|
||||
customOutboundContent = customOutboundContent.Replace("{{interface}}", outboundBindInterface);
|
||||
var customOutboundObj = JsonUtils.ParseJson(customOutboundContent) as JsonObject;
|
||||
|
||||
if (!containTagPlaceholder)
|
||||
{
|
||||
customOutboundObj?["tag"] = outboundTag;
|
||||
}
|
||||
if (!containDetourPlaceholder && !outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj!["streamSettings"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"]["dialerProxy"] = outboundDetour;
|
||||
if (customOutboundObj["streamSettings"]?["xhttpSettings"]?["extra"]?["downloadSettings"] is JsonObject downloadSettings)
|
||||
{
|
||||
downloadSettings["sockopt"] ??= new JsonObject();
|
||||
downloadSettings["sockopt"]["dialerProxy"] = outboundDetour;
|
||||
}
|
||||
}
|
||||
else if (outboundDetour.IsNullOrEmpty())
|
||||
{
|
||||
(customOutboundObj?["streamSettings"]?["sockopt"] as JsonObject)?.Remove("dialerProxy");
|
||||
}
|
||||
if (!containBindInterfacePlaceholder && !outboundBindInterface.IsNullOrEmpty())
|
||||
{
|
||||
customOutboundObj!["streamSettings"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"] ??= new JsonObject();
|
||||
customOutboundObj["streamSettings"]["sockopt"]["interface"] = outboundBindInterface;
|
||||
if (customOutboundObj["streamSettings"]?["xhttpSettings"]?["extra"]?["downloadSettings"] is JsonObject downloadSettings)
|
||||
{
|
||||
downloadSettings["sockopt"] ??= new JsonObject();
|
||||
downloadSettings["sockopt"]["interface"] = outboundBindInterface;
|
||||
}
|
||||
}
|
||||
|
||||
var index = coreConfigOutboundsNode
|
||||
.Select((node, idx) => new { node, idx })
|
||||
.FirstOrDefault(x => x.node?["tag"]?.ToString() == outboundTag)?.idx ?? -1;
|
||||
if (index != -1)
|
||||
{
|
||||
coreConfigOutboundsNode[index] = customOutboundObj;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonUtils.Serialize(coreConfigNode);
|
||||
}
|
||||
|
||||
private string ApplyFullConfigTemplate(string coreConfigContent)
|
||||
{
|
||||
var fullConfigTemplate = context.FullConfigTemplate;
|
||||
if (fullConfigTemplate is not { Enabled: true })
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config;
|
||||
if (fullConfigTemplateItem.IsNullOrEmpty())
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem);
|
||||
if (fullConfigTemplateNode == null)
|
||||
{
|
||||
return JsonUtils.Serialize(_coreConfig);
|
||||
return coreConfigContent;
|
||||
}
|
||||
|
||||
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||
@@ -74,8 +155,8 @@ public partial class CoreConfigV2rayService
|
||||
else
|
||||
{
|
||||
var subjectSelector = _coreConfig.observatory.subjectSelector;
|
||||
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||
subjectSelector?.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["observatory"]?["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector?.Distinct().ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,16 +169,18 @@ public partial class CoreConfigV2rayService
|
||||
else
|
||||
{
|
||||
var subjectSelector = _coreConfig.burstObservatory.subjectSelector;
|
||||
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||
subjectSelector?.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||
fullConfigTemplateNode["burstObservatory"]?["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector?.Distinct().ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
var customOutboundsNode = new JsonArray();
|
||||
|
||||
foreach (var outbound in _coreConfig.outbounds)
|
||||
var coreConfigNode = JsonNode.Parse(coreConfigContent);
|
||||
var coreConfigOutboundsNode = coreConfigNode?["outbounds"] as JsonArray ?? [];
|
||||
foreach (var outbound in coreConfigOutboundsNode)
|
||||
{
|
||||
if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom")
|
||||
if (outbound?["protocol"]?.ToString()?.ToLower() is "blackhole" or "dns" or "freedom")
|
||||
{
|
||||
if (fullConfigTemplate.AddProxyOnly == true)
|
||||
{
|
||||
@@ -105,14 +188,22 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()
|
||||
&& (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true))
|
||||
&& (outbound["streamSettings"]?["sockopt"]?["dialerProxy"].ToString().IsNullOrEmpty() ?? true))
|
||||
{
|
||||
var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address
|
||||
?? outbound.settings?.vnext?.FirstOrDefault()?.address
|
||||
var outboundAddress = outbound["settings"]?["servers"]?.AsArray()?.FirstOrDefault()?["address"]?.ToString()
|
||||
?? outbound["settings"]?["vnext"]?.AsArray()?.FirstOrDefault()?["address"]?.ToString()
|
||||
?? string.Empty;
|
||||
if (!Utils.IsPrivateNetwork(outboundAddress))
|
||||
{
|
||||
FillDialerProxy(outbound, fullConfigTemplate.ProxyDetour);
|
||||
//FillDialerProxy(outbound, fullConfigTemplate.ProxyDetour);
|
||||
outbound["streamSettings"] ??= new JsonObject();
|
||||
outbound["streamSettings"]["sockopt"] ??= new JsonObject();
|
||||
outbound["streamSettings"]["sockopt"]["dialerProxy"] = fullConfigTemplate.ProxyDetour;
|
||||
if (outbound["streamSettings"]?["xhttpSettings"]?["extra"]?["downloadSettings"] is JsonObject downloadSettings)
|
||||
{
|
||||
downloadSettings["sockopt"] ??= new JsonObject();
|
||||
downloadSettings["sockopt"]["dialerProxy"] = fullConfigTemplate.ProxyDetour;
|
||||
}
|
||||
}
|
||||
}
|
||||
customOutboundsNode.Add(JsonUtils.DeepCopy(outbound));
|
||||
|
||||
@@ -52,6 +52,12 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||
if (_node.ConfigType == EConfigType.Outbound)
|
||||
{
|
||||
outbound.tag = baseTagName;
|
||||
context.CustomOutboundMap[outbound] = _node.IndexId;
|
||||
return outbound;
|
||||
}
|
||||
FillOutbound(outbound);
|
||||
outbound.tag = baseTagName;
|
||||
return outbound;
|
||||
@@ -404,7 +410,6 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
TlsSettings4Ray tlsSettings = new()
|
||||
{
|
||||
allowInsecure = _node.GetAllowInsecure(),
|
||||
alpn = _node.GetAlpn(),
|
||||
fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint,
|
||||
echConfigList = _node.EchConfigList.NullIfEmpty(),
|
||||
@@ -438,12 +443,10 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
tlsSettings.certificates = certsettings;
|
||||
tlsSettings.disableSystemRoot = true;
|
||||
tlsSettings.allowInsecure = false;
|
||||
}
|
||||
else if (!_node.CertSha.IsNullOrEmpty())
|
||||
{
|
||||
tlsSettings.pinnedPeerCertSha256 = _node.CertSha;
|
||||
tlsSettings.allowInsecure = false;
|
||||
}
|
||||
streamSettings.tlsSettings = tlsSettings;
|
||||
}
|
||||
@@ -788,12 +791,12 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
else if (chainStartNodes.Count > 1)
|
||||
{
|
||||
var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds);
|
||||
var existedChainNodes = CloneOutbounds(resultOutbounds);
|
||||
resultOutbounds.Clear();
|
||||
var j = 0;
|
||||
foreach (var chainStartNode in chainStartNodes)
|
||||
{
|
||||
var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes);
|
||||
var existedChainNodesClone = CloneOutbounds(existedChainNodes);
|
||||
foreach (var existedChainNode in existedChainNodesClone)
|
||||
{
|
||||
var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}";
|
||||
@@ -955,4 +958,19 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
return fragmentMask;
|
||||
}
|
||||
|
||||
private List<Outbounds4Ray> CloneOutbounds(List<Outbounds4Ray> outbounds)
|
||||
{
|
||||
var clonedOutbounds = new List<Outbounds4Ray>();
|
||||
foreach (var outbound in outbounds)
|
||||
{
|
||||
var clonedOutbound = JsonUtils.DeepCopy(outbound);
|
||||
clonedOutbounds.Add(clonedOutbound);
|
||||
if (context.CustomOutboundMap.ContainsKey(outbound))
|
||||
{
|
||||
context.CustomOutboundMap[clonedOutbound] = context.CustomOutboundMap[outbound];
|
||||
}
|
||||
}
|
||||
return clonedOutbounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,8 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
if (node == null
|
||||
|| (!Global.XraySupportConfigType.Contains(node.ConfigType)
|
||||
&& !node.ConfigType.IsGroupType()))
|
||||
&& !node.ConfigType.IsGroupType()
|
||||
&& node.ConfigType is not EConfigType.Outbound))
|
||||
{
|
||||
return Global.ProxyTag;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
||||
public partial class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
public partial ProfileItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedChild { get; set; }
|
||||
public partial ProfileItem SelectedChild { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public IList<ProfileItem> SelectedChildren { get; set; }
|
||||
public partial IList<ProfileItem> SelectedChildren { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
public partial string? CoreType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? PolicyGroupType { get; set; }
|
||||
public partial string? PolicyGroupType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public SubItem? SelectedSubItem { get; set; }
|
||||
public partial SubItem? SelectedSubItem { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? Filter { get; set; }
|
||||
public partial string? Filter { get; set; }
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||
|
||||
public IObservableCollection<ProfileItem> ChildItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
public BulkObservableCollection<ProfileItem> ChildItemsObs { get; } = [];
|
||||
|
||||
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
public BulkObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = [];
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoveCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveTopCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveUpCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveDownCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveBottomCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public AddGroupServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
public partial class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<Unit, string?> BrowseConfigFileInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> BrowseConfigFileInteraction { get; } = new();
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
public partial ProfileItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
public partial string? CoreType { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> BrowseServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> EditServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveServerCmd { get; }
|
||||
[Reactive]
|
||||
public partial bool IsSingboxEndpoint { get; set; }
|
||||
|
||||
public ReactiveCommand<RxVoid, RxVoid> BrowseServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> EditServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveServerCmd { get; }
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
public AddServer2ViewModel(ProfileItem profileItem)
|
||||
@@ -23,7 +26,7 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
|
||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var fileName = await BrowseConfigFileInteraction.Handle(Unit.Default);
|
||||
var fileName = await BrowseConfigFileInteraction.Handle(RxVoid.Default);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
@@ -40,7 +43,10 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
});
|
||||
|
||||
SelectedSource = profileItem.IndexId.IsNullOrEmpty() ? profileItem : JsonUtils.DeepCopy(profileItem);
|
||||
CoreType = SelectedSource?.CoreType?.ToString();
|
||||
var coreStr = SelectedSource?.CoreType?.ToString();
|
||||
coreStr = coreStr.IsNullOrEmpty() ? Global.CoreTypes.FirstOrDefault() : coreStr;
|
||||
CoreType = coreStr;
|
||||
IsSingboxEndpoint = SelectedSource?.GetProtocolExtra()?.IsSingboxEndpoint ?? false;
|
||||
}
|
||||
|
||||
private async Task SaveServerAsync()
|
||||
@@ -58,6 +64,10 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
return;
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
|
||||
SelectedSource.SetProtocolExtra(SelectedSource?.GetProtocolExtra() with
|
||||
{
|
||||
IsSingboxEndpoint = IsSingboxEndpoint ? true : null,
|
||||
});
|
||||
|
||||
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
@@ -80,7 +90,8 @@ public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
var item = await AppManager.Instance.GetProfileItem(SelectedSource.IndexId);
|
||||
item ??= SelectedSource;
|
||||
item.Address = fileName;
|
||||
if (await ConfigHandler.AddCustomServer(_config, item, false) == 0)
|
||||
var result = item.ConfigType == EConfigType.Outbound ? await ConfigHandler.AddCustomOutboundServer(_config, item, false) : await ConfigHandler.AddCustomServer(_config, item, false);
|
||||
if (result == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedCustomServer);
|
||||
if (item.IndexId.IsNotEmpty())
|
||||
|
||||
@@ -1,131 +1,131 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
public partial class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
public partial ProfileItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? CoreType { get; set; }
|
||||
public partial string? CoreType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AllowInsecure { get; set; }
|
||||
public partial bool AllowInsecure { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool MuxEnabled { get; set; }
|
||||
public partial bool MuxEnabled { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Cert { get; set; }
|
||||
public partial string Cert { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CertTip { get; set; }
|
||||
public partial string CertTip { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CertSha { get; set; }
|
||||
public partial string CertSha { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SalamanderPass { get; set; }
|
||||
public partial string SalamanderPass { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int AlterId { get; set; }
|
||||
public partial int AlterId { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Ports { get; set; }
|
||||
public partial string Ports { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? UpMbps { get; set; }
|
||||
public partial int? UpMbps { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? DownMbps { get; set; }
|
||||
public partial int? DownMbps { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HopInterval { get; set; }
|
||||
public partial string HopInterval { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Flow { get; set; }
|
||||
public partial string Flow { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string VmessSecurity { get; set; }
|
||||
public partial string VmessSecurity { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string VlessEncryption { get; set; }
|
||||
public partial string VlessEncryption { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SsMethod { get; set; }
|
||||
public partial string SsMethod { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgPublicKey { get; set; }
|
||||
public partial string WgPublicKey { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgPresharedKey { get; set; }
|
||||
public partial string WgPresharedKey { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgInterfaceAddress { get; set; }
|
||||
public partial string WgInterfaceAddress { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string WgReserved { get; set; }
|
||||
public partial string WgReserved { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int WgMtu { get; set; }
|
||||
public partial int WgMtu { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool Uot { get; set; }
|
||||
public partial bool Uot { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string CongestionControl { get; set; }
|
||||
public partial string CongestionControl { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? InsecureConcurrency { get; set; }
|
||||
public partial int? InsecureConcurrency { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool NaiveQuic { get; set; }
|
||||
public partial bool NaiveQuic { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HttpHeadersJson { get; set; }
|
||||
public partial string HttpHeadersJson { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Hy2RealmUrl { get; set; }
|
||||
public partial string Hy2RealmUrl { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMinPacketSize { get; set; }
|
||||
public partial int GeckoMinPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int GeckoMaxPacketSize { get; set; }
|
||||
public partial int GeckoMaxPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RawHeaderType { get; set; }
|
||||
public partial string RawHeaderType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Host { get; set; }
|
||||
public partial string Host { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Path { get; set; }
|
||||
public partial string Path { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string XhttpMode { get; set; }
|
||||
public partial string XhttpMode { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string XhttpExtra { get; set; }
|
||||
public partial string XhttpExtra { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string GrpcAuthority { get; set; }
|
||||
public partial string GrpcAuthority { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string GrpcServiceName { get; set; }
|
||||
public partial string GrpcServiceName { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string GrpcMode { get; set; }
|
||||
public partial string GrpcMode { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string KcpHeaderType { get; set; }
|
||||
public partial string KcpHeaderType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string KcpSeed { get; set; }
|
||||
public partial string KcpSeed { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int? KcpMtu { get; set; }
|
||||
public partial int? KcpMtu { get; set; }
|
||||
|
||||
public string TransportHeaderType
|
||||
{
|
||||
@@ -239,9 +239,9 @@ public class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
}
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FetchCertCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public AddServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class BackupAndRestoreViewModel : MyReactiveObject
|
||||
public partial class BackupAndRestoreViewModel : MyReactiveObject
|
||||
{
|
||||
private readonly string _guiConfigs = "guiConfigs";
|
||||
private static string BackupFileName => $"backup_{DateTime.Now:yyyyMMddHHmmss}.zip";
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RemoteBackupCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoteRestoreCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> WebDavCheckCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoteBackupCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoteRestoreCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> WebDavCheckCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public WebDavItem SelectedSource { get; set; }
|
||||
public partial WebDavItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string OperationMsg { get; set; } = string.Empty;
|
||||
public partial string OperationMsg { get; set; } = string.Empty;
|
||||
|
||||
public BackupAndRestoreViewModel()
|
||||
{
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class CheckUpdateViewModel : MyReactiveObject
|
||||
public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
{
|
||||
private const string _geo = "GeoFiles";
|
||||
private readonly ECoreType _v2rayN = ECoreType.v2rayN;
|
||||
private List<CheckUpdateModel> _lstUpdated = [];
|
||||
private static readonly string _tag = "CheckUpdateViewModel";
|
||||
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
public EventChannel<RxVoid> ReloadRequested { get; } = new();
|
||||
|
||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CheckOnlyCmd { get; }
|
||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = [];
|
||||
public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; }
|
||||
[Reactive] public partial bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
|
||||
public CheckUpdateViewModel()
|
||||
{
|
||||
@@ -288,10 +288,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
|
||||
private async Task UpdateFinishedSub(bool blReload)
|
||||
{
|
||||
RxSchedulers.MainThreadScheduler.Schedule(blReload, (scheduler, blReload) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = UpdateFinishedResult(blReload);
|
||||
return Disposable.Empty;
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
@@ -404,10 +403,9 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
Remarks = msg,
|
||||
};
|
||||
|
||||
RxSchedulers.MainThreadScheduler.Schedule(item, (scheduler, model) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = UpdateViewResult(model);
|
||||
return Disposable.Empty;
|
||||
_ = UpdateViewResult(item);
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ClashConnectionsViewModel : MyReactiveObject
|
||||
public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||
{
|
||||
public IObservableCollection<ClashConnectionModel> ConnectionItems { get; } = new ObservableCollectionExtended<ClashConnectionModel>();
|
||||
public BulkObservableCollection<ClashConnectionModel> ConnectionItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ClashConnectionModel SelectedSource { get; set; }
|
||||
public partial ClashConnectionModel SelectedSource { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ConnectionCloseCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ConnectionCloseAllCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ConnectionCloseAllCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public string HostFilter { get; set; }
|
||||
public partial string HostFilter { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
public partial bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashConnectionsViewModel()
|
||||
{
|
||||
@@ -55,10 +55,9 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
RxSchedulers.MainThreadScheduler.Schedule(ret?.connections, (scheduler, model) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = RefreshConnections(model);
|
||||
return Disposable.Empty;
|
||||
_ = RefreshConnections(ret?.connections);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
using System.Reactive.Concurrency;
|
||||
using static ServiceLib.Models.Dto.ClashProviders;
|
||||
using static ServiceLib.Models.Dto.ClashProxies;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ClashProxiesViewModel : MyReactiveObject
|
||||
public partial class ClashProxiesViewModel : MyReactiveObject
|
||||
{
|
||||
private Dictionary<string, ProxiesItem>? _proxies;
|
||||
private Dictionary<string, ProvidersItem>? _providers;
|
||||
private readonly int _delayTimeout = 99999999;
|
||||
|
||||
public IObservableCollection<ClashProxyModel> ProxyGroups { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||
public IObservableCollection<ClashProxyModel> ProxyDetails { get; } = new ObservableCollectionExtended<ClashProxyModel>();
|
||||
public BulkObservableCollection<ClashProxyModel> ProxyGroups { get; } = [];
|
||||
public BulkObservableCollection<ClashProxyModel> ProxyDetails { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ClashProxyModel SelectedGroup { get; set; }
|
||||
public partial ClashProxyModel SelectedGroup { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ClashProxyModel SelectedDetail { get; set; }
|
||||
public partial ClashProxyModel SelectedDetail { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ProxiesReloadCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesDelayTestCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesDelayTestPartCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ProxiesSelectActivityCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesReloadCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesDelayTestPartCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ProxiesSelectActivityCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public int RuleModeSelected { get; set; }
|
||||
public partial int RuleModeSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int SortingSelected { get; set; }
|
||||
public partial int SortingSelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
public partial bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashProxiesViewModel()
|
||||
{
|
||||
@@ -379,10 +378,9 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
var model = new SpeedTestResult() { IndexId = item.Name, Delay = result };
|
||||
RxSchedulers.MainThreadScheduler.Schedule(model, (scheduler, model) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = ProxiesDelayTestResult(model);
|
||||
return Disposable.Empty;
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||
public partial class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive] public bool UseSystemHosts { get; set; }
|
||||
[Reactive] public bool AddCommonHosts { get; set; }
|
||||
[Reactive] public bool FakeIP { get; set; }
|
||||
[Reactive] public string FakeIPRange { get; set; }
|
||||
[Reactive] public bool BlockBindingQuery { get; set; }
|
||||
[Reactive] public string DirectDNS { get; set; }
|
||||
[Reactive] public string RemoteDNS { get; set; }
|
||||
[Reactive] public string BootstrapDNS { get; set; }
|
||||
[Reactive] public string Strategy4Freedom { get; set; }
|
||||
[Reactive] public string Strategy4Proxy { get; set; }
|
||||
[Reactive] public string Strategy4ProxyDial { get; set; }
|
||||
[Reactive] public string Hosts { get; set; }
|
||||
[Reactive] public string DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool ParallelQuery { get; set; }
|
||||
[Reactive] public bool ServeStale { get; set; }
|
||||
[Reactive] public bool EnableHappyEyeballs { get; set; }
|
||||
[Reactive] public partial bool UseSystemHosts { get; set; }
|
||||
[Reactive] public partial bool AddCommonHosts { get; set; }
|
||||
[Reactive] public partial bool FakeIP { get; set; }
|
||||
[Reactive] public partial string FakeIPRange { get; set; }
|
||||
[Reactive] public partial bool BlockBindingQuery { get; set; }
|
||||
[Reactive] public partial string DirectDNS { get; set; }
|
||||
[Reactive] public partial string RemoteDNS { get; set; }
|
||||
[Reactive] public partial string BootstrapDNS { get; set; }
|
||||
[Reactive] public partial string Strategy4Freedom { get; set; }
|
||||
[Reactive] public partial string Strategy4Proxy { get; set; }
|
||||
[Reactive] public partial string Strategy4ProxyDial { get; set; }
|
||||
[Reactive] public partial string Hosts { get; set; }
|
||||
[Reactive] public partial string DirectExpectedIPs { get; set; }
|
||||
[Reactive] public partial bool ParallelQuery { get; set; }
|
||||
[Reactive] public partial bool ServeStale { get; set; }
|
||||
[Reactive] public partial bool EnableHappyEyeballs { get; set; }
|
||||
|
||||
[Reactive] public bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string NormalDNSCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string TunDNSCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public partial string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string NormalDNSCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string TunDNSCompatible { get; set; } = string.Empty;
|
||||
|
||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string NormalDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string TunDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public partial string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string NormalDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial string TunDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public partial bool RayCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public partial bool SBCustomDNSEnableCompatible { get; set; }
|
||||
|
||||
[ObservableAsProperty] public bool IsSimpleDNSEnabled { get; }
|
||||
public bool IsSimpleDNSEnabled => !(RayCustomDNSEnableCompatible && SBCustomDNSEnableCompatible);
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||
|
||||
public DNSSettingViewModel()
|
||||
{
|
||||
@@ -60,8 +60,7 @@ public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible)
|
||||
.Select(x => x is not { Item1: true, Item2: true })
|
||||
.ToPropertyEx(this, x => x.IsSimpleDNSEnabled);
|
||||
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsSimpleDNSEnabled)));
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
||||
public partial class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Reactive
|
||||
|
||||
[Reactive]
|
||||
public bool EnableFullConfigTemplate4Ray { get; set; }
|
||||
public partial bool EnableFullConfigTemplate4Ray { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||
public partial bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
public partial string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
public partial string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
public partial string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
public partial string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public bool AddProxyOnly4Ray { get; set; }
|
||||
public partial bool AddProxyOnly4Ray { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AddProxyOnly4Singbox { get; set; }
|
||||
public partial bool AddProxyOnly4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Ray { get; set; } = string.Empty;
|
||||
public partial string ProxyDetour4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Singbox { get; set; } = string.Empty;
|
||||
public partial string ProxyDetour4Singbox { get; set; } = string.Empty;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
#endregion Reactive
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject, ICloseable
|
||||
|
||||
private readonly List<KeyEventItem> _globalHotkeys;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public GlobalHotkeySettingViewModel()
|
||||
{
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Reactive.Concurrency;
|
||||
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : MyReactiveObject
|
||||
public partial class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<Unit, byte[]?> ScanScreenInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> BrowseImageFileInteraction { get; } = new();
|
||||
public Interaction<bool?, Unit> ShowHideWindowInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<RxVoid, byte[]?> ScanScreenInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> BrowseImageFileInteraction { get; } = new();
|
||||
public Interaction<bool?, RxVoid> ShowHideWindowInteraction { get; } = new();
|
||||
|
||||
public bool DesignMode { get; set; }
|
||||
|
||||
@@ -22,70 +20,73 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
#region Menu
|
||||
|
||||
//servers
|
||||
public ReactiveCommand<Unit, Unit> AddVmessServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddVmessServerCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddVlessServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddShadowsocksServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddHttpServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddWireguardServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddAnytlsServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddNaiveServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddPolicyGroupServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddProxyChainServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaImageCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddVlessServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddShadowsocksServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddSocksServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddHttpServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddTrojanServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddHysteria2ServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddTuicServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddWireguardServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddAnytlsServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddNaiveServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCustomServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCustomOutboundServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddPolicyGroupServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddProxyChainServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaImageCmd { get; }
|
||||
|
||||
//Subscription
|
||||
public ReactiveCommand<Unit, Unit> SubSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubSettingCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubGroupUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubGroupUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubGroupUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubGroupUpdateViaProxyCmd { get; }
|
||||
|
||||
//Setting
|
||||
public ReactiveCommand<Unit, Unit> OptionSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> OptionSettingCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RoutingSettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> DNSSettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> FullConfigTemplateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GlobalHotkeySettingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RebootAsAdminCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ClearServerStatisticsCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> OpenTheFileLocationCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RoutingSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> DNSSettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FullConfigTemplateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> GlobalHotkeySettingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RebootAsAdminCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ClearServerStatisticsCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> OpenTheFileLocationCmd { get; }
|
||||
|
||||
//Presets
|
||||
public ReactiveCommand<Unit, Unit> RegionalPresetDefaultCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RegionalPresetDefaultCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RegionalPresetRussiaCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RegionalPresetRussiaCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RegionalPresetIranCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RegionalPresetIranCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ReloadCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ReloadCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlReloadEnabled { get; set; }
|
||||
public partial bool BlReloadEnabled { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool ShowClashUI { get; set; }
|
||||
public partial bool ShowClashUI { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int TabMainSelectedIndex { get; set; }
|
||||
public partial int TabMainSelectedIndex { get; set; }
|
||||
|
||||
[Reactive] public bool BlIsWindows { get; set; }
|
||||
[Reactive] public partial bool BlIsWindows { get; set; }
|
||||
|
||||
[Reactive] public bool BlNewUpdate { get; set; }
|
||||
[Reactive] public partial bool BlNewUpdate { get; set; }
|
||||
|
||||
[Reactive] public EGirdOrientation MainGirdOrientation { get; set; }
|
||||
[Reactive] public partial EGirdOrientation MainGirdOrientation { get; set; }
|
||||
|
||||
#endregion Menu
|
||||
|
||||
private readonly SynchronizationContext _uiContext = SynchronizationContext.Current;
|
||||
|
||||
#region Init
|
||||
|
||||
public MainWindowViewModel()
|
||||
@@ -145,6 +146,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await AddServerAsync(EConfigType.Custom);
|
||||
});
|
||||
AddCustomOutboundServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(EConfigType.Outbound);
|
||||
});
|
||||
AddPolicyGroupServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(EConfigType.PolicyGroup);
|
||||
@@ -268,7 +273,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshServers());
|
||||
|
||||
var vmReloadRequestedList = new List<IObservable<Unit>>
|
||||
var vmReloadRequestedList = new List<IObservable<RxVoid>>
|
||||
{
|
||||
ProfilesViewModel.ReloadRequested.AsObservable(),
|
||||
StatusBarViewModel.ReloadRequested.AsObservable(),
|
||||
@@ -407,12 +412,15 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshServersDispatcherAsync()
|
||||
{
|
||||
await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||
//await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||
_uiContext?.Post(_ => _ = RefreshServers(), null);
|
||||
}
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||
//await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||
|
||||
_uiContext?.Post(_ => _ = ProfilesViewModel.RefreshSubscriptions(), null);
|
||||
}
|
||||
|
||||
#endregion Servers && Groups
|
||||
@@ -429,7 +437,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
};
|
||||
|
||||
bool? ret = false;
|
||||
if (eConfigType == EConfigType.Custom)
|
||||
if (eConfigType is EConfigType.Custom or EConfigType.Outbound)
|
||||
{
|
||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||
@@ -459,7 +467,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
var stringData = clipboardData;
|
||||
if (clipboardData == null)
|
||||
{
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
@@ -482,7 +490,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaScanAsync()
|
||||
{
|
||||
var result = await ScanScreenInteraction.Handle(Unit.Default);
|
||||
var result = await ScanScreenInteraction.Handle(RxVoid.Default);
|
||||
await ScanScreenResult(result);
|
||||
}
|
||||
|
||||
@@ -494,7 +502,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaImageAsync()
|
||||
{
|
||||
var imageFileName = await BrowseImageFileInteraction.Handle(Unit.Default);
|
||||
var imageFileName = await BrowseImageFileInteraction.Handle(RxVoid.Default);
|
||||
await AddScanResultAsync(imageFileName);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MsgViewModel : MyReactiveObject
|
||||
public partial class MsgViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, Unit> DispatcherShowMsgInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> DispatcherShowMsgInteraction { get; } = new();
|
||||
|
||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||
private volatile bool _lastMsgFilterNotAvailable;
|
||||
@@ -10,10 +10,10 @@ public class MsgViewModel : MyReactiveObject
|
||||
public int NumMaxMsg { get; } = 500;
|
||||
|
||||
[Reactive]
|
||||
public string MsgFilter { get; set; }
|
||||
public partial string MsgFilter { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
public partial bool AutoRefresh { get; set; }
|
||||
|
||||
public MsgViewModel()
|
||||
{
|
||||
@@ -36,6 +36,11 @@ public class MsgViewModel : MyReactiveObject
|
||||
.Subscribe(content => _ = AppendQueueMsg(content));
|
||||
}
|
||||
|
||||
public void FlushQueueMsg()
|
||||
{
|
||||
_ = AppendQueueMsg(string.Empty);
|
||||
}
|
||||
|
||||
private async Task AppendQueueMsg(string msg)
|
||||
{
|
||||
if (AutoRefresh == false)
|
||||
@@ -65,7 +70,17 @@ public class MsgViewModel : MyReactiveObject
|
||||
sb.Append(line);
|
||||
}
|
||||
|
||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_queueMsg.Enqueue(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -75,6 +90,11 @@ public class MsgViewModel : MyReactiveObject
|
||||
|
||||
private void EnqueueQueueMsg(string msg)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//filter msg
|
||||
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
|
||||
{
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Core
|
||||
|
||||
[Reactive] public int LocalPort { get; set; }
|
||||
[Reactive] public bool SecondLocalPortEnabled { get; set; }
|
||||
[Reactive] public bool UdpEnabled { get; set; }
|
||||
[Reactive] public bool SniffingEnabled { get; set; }
|
||||
[Reactive] public partial int LocalPort { get; set; }
|
||||
[Reactive] public partial bool SecondLocalPortEnabled { get; set; }
|
||||
[Reactive] public partial bool UdpEnabled { get; set; }
|
||||
[Reactive] public partial bool SniffingEnabled { get; set; }
|
||||
public IList<string> DestOverride { get; set; }
|
||||
[Reactive] public bool RouteOnly { get; set; }
|
||||
[Reactive] public bool AllowLANConn { get; set; }
|
||||
[Reactive] public bool NewPort4LAN { get; set; }
|
||||
[Reactive] public string User { get; set; }
|
||||
[Reactive] public string Pass { get; set; }
|
||||
[Reactive] public bool LogEnabled { get; set; }
|
||||
[Reactive] public string Loglevel { get; set; }
|
||||
[Reactive] public string DefFingerprint { get; set; }
|
||||
[Reactive] public string DefUserAgent { get; set; }
|
||||
[Reactive] public string SendThrough { get; set; }
|
||||
[Reactive] public string BindInterface { get; set; }
|
||||
[Reactive] public string Mux4SboxProtocol { get; set; }
|
||||
[Reactive] public bool EnableCacheFile4Sbox { get; set; }
|
||||
[Reactive] public int? HyUpMbps { get; set; }
|
||||
[Reactive] public int? HyDownMbps { get; set; }
|
||||
[Reactive] public bool EnableFragment { get; set; }
|
||||
[Reactive] public bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public string FragmentPackets { get; set; }
|
||||
[Reactive] public string FragmentLengths { get; set; }
|
||||
[Reactive] public string FragmentDelays { get; set; }
|
||||
[Reactive] public string FragmentMaxSplit { get; set; }
|
||||
[Reactive] public partial bool RouteOnly { get; set; }
|
||||
[Reactive] public partial bool AllowLANConn { get; set; }
|
||||
[Reactive] public partial bool NewPort4LAN { get; set; }
|
||||
[Reactive] public partial string User { get; set; }
|
||||
[Reactive] public partial string Pass { get; set; }
|
||||
[Reactive] public partial bool LogEnabled { get; set; }
|
||||
[Reactive] public partial string Loglevel { get; set; }
|
||||
[Reactive] public partial string DefFingerprint { get; set; }
|
||||
[Reactive] public partial string DefUserAgent { get; set; }
|
||||
[Reactive] public partial string SendThrough { get; set; }
|
||||
[Reactive] public partial string BindInterface { get; set; }
|
||||
[Reactive] public partial string Mux4SboxProtocol { get; set; }
|
||||
[Reactive] public partial bool EnableCacheFile4Sbox { get; set; }
|
||||
[Reactive] public partial int? HyUpMbps { get; set; }
|
||||
[Reactive] public partial int? HyDownMbps { get; set; }
|
||||
[Reactive] public partial bool EnableFragment { get; set; }
|
||||
[Reactive] public partial bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public partial string FragmentPackets { get; set; }
|
||||
[Reactive] public partial string FragmentLengths { get; set; }
|
||||
[Reactive] public partial string FragmentDelays { get; set; }
|
||||
[Reactive] public partial string FragmentMaxSplit { get; set; }
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region UI
|
||||
|
||||
[Reactive] public bool AutoRun { get; set; }
|
||||
[Reactive] public bool EnableStatistics { get; set; }
|
||||
[Reactive] public bool KeepOlderDedupl { get; set; }
|
||||
[Reactive] public bool DisplayRealTimeSpeed { get; set; }
|
||||
[Reactive] public bool EnableAutoAdjustMainLvColWidth { get; set; }
|
||||
[Reactive] public bool AutoHideStartup { get; set; }
|
||||
[Reactive] public bool Hide2TrayWhenClose { get; set; }
|
||||
[Reactive] public bool MacOSShowInDock { get; set; }
|
||||
[Reactive] public bool EnableDragDropSort { get; set; }
|
||||
[Reactive] public bool DoubleClick2Activate { get; set; }
|
||||
[Reactive] public int AutoUpdateInterval { get; set; }
|
||||
[Reactive] public int TrayMenuServersLimit { get; set; }
|
||||
[Reactive] public string CurrentFontFamily { get; set; }
|
||||
[Reactive] public int SpeedTestTimeout { get; set; }
|
||||
[Reactive] public string SpeedTestUrl { get; set; }
|
||||
[Reactive] public string SpeedPingTestUrl { get; set; }
|
||||
[Reactive] public string UdpTestTarget { get; set; }
|
||||
[Reactive] public int MixedConcurrencyCount { get; set; }
|
||||
[Reactive] public bool EnableHWA { get; set; }
|
||||
[Reactive] public string SubConvertUrl { get; set; }
|
||||
[Reactive] public int MainGirdOrientation { get; set; }
|
||||
[Reactive] public string GeoFileSourceUrl { get; set; }
|
||||
[Reactive] public string SrsFileSourceUrl { get; set; }
|
||||
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
||||
[Reactive] public string IPAPIUrl { get; set; }
|
||||
[Reactive] public string RootCertProvider { get; set; }
|
||||
[Reactive] public partial bool AutoRun { get; set; }
|
||||
[Reactive] public partial bool EnableStatistics { get; set; }
|
||||
[Reactive] public partial bool KeepOlderDedupl { get; set; }
|
||||
[Reactive] public partial bool DisplayRealTimeSpeed { get; set; }
|
||||
[Reactive] public partial bool EnableAutoAdjustMainLvColWidth { get; set; }
|
||||
[Reactive] public partial bool AutoHideStartup { get; set; }
|
||||
[Reactive] public partial bool Hide2TrayWhenClose { get; set; }
|
||||
[Reactive] public partial bool MacOSShowInDock { get; set; }
|
||||
[Reactive] public partial bool EnableDragDropSort { get; set; }
|
||||
[Reactive] public partial bool DoubleClick2Activate { get; set; }
|
||||
[Reactive] public partial int AutoUpdateInterval { get; set; }
|
||||
[Reactive] public partial int TrayMenuServersLimit { get; set; }
|
||||
[Reactive] public partial string CurrentFontFamily { get; set; }
|
||||
[Reactive] public partial int SpeedTestTimeout { get; set; }
|
||||
[Reactive] public partial string SpeedTestUrl { get; set; }
|
||||
[Reactive] public partial string SpeedPingTestUrl { get; set; }
|
||||
[Reactive] public partial string UdpTestTarget { get; set; }
|
||||
[Reactive] public partial int MixedConcurrencyCount { get; set; }
|
||||
[Reactive] public partial bool EnableHWA { get; set; }
|
||||
[Reactive] public partial string SubConvertUrl { get; set; }
|
||||
[Reactive] public partial int MainGirdOrientation { get; set; }
|
||||
[Reactive] public partial string GeoFileSourceUrl { get; set; }
|
||||
[Reactive] public partial string SrsFileSourceUrl { get; set; }
|
||||
[Reactive] public partial string RoutingRulesSourceUrl { get; set; }
|
||||
[Reactive] public partial string IPAPIUrl { get; set; }
|
||||
[Reactive] public partial string RootCertProvider { get; set; }
|
||||
|
||||
#endregion UI
|
||||
|
||||
#region UI visibility
|
||||
|
||||
[Reactive] public bool BlIsWindows { get; set; }
|
||||
[Reactive] public bool BlIsLinux { get; set; }
|
||||
[Reactive] public bool BlIsIsMacOS { get; set; }
|
||||
[Reactive] public bool BlIsNonWindows { get; set; }
|
||||
[Reactive] public partial bool BlIsWindows { get; set; }
|
||||
[Reactive] public partial bool BlIsLinux { get; set; }
|
||||
[Reactive] public partial bool BlIsIsMacOS { get; set; }
|
||||
[Reactive] public partial bool BlIsNonWindows { get; set; }
|
||||
|
||||
#endregion UI visibility
|
||||
|
||||
#region System proxy
|
||||
|
||||
[Reactive] public bool NotProxyLocalAddress { get; set; }
|
||||
[Reactive] public string SystemProxyAdvancedProtocol { get; set; }
|
||||
[Reactive] public string SystemProxyExceptions { get; set; }
|
||||
[Reactive] public string CustomSystemProxyPacPath { get; set; }
|
||||
[Reactive] public string CustomSystemProxyScriptPath { get; set; }
|
||||
[Reactive] public partial bool NotProxyLocalAddress { get; set; }
|
||||
[Reactive] public partial string SystemProxyAdvancedProtocol { get; set; }
|
||||
[Reactive] public partial string SystemProxyExceptions { get; set; }
|
||||
[Reactive] public partial string CustomSystemProxyPacPath { get; set; }
|
||||
[Reactive] public partial string CustomSystemProxyScriptPath { get; set; }
|
||||
|
||||
#endregion System proxy
|
||||
|
||||
#region Tun mode
|
||||
|
||||
[Reactive] public bool TunAutoRoute { get; set; }
|
||||
[Reactive] public bool TunStrictRoute { get; set; }
|
||||
[Reactive] public string TunStack { get; set; }
|
||||
[Reactive] public int TunMtu { get; set; }
|
||||
[Reactive] public bool TunEnableIPv6Address { get; set; }
|
||||
[Reactive] public string TunIcmpRouting { get; set; }
|
||||
[Reactive] public bool TunEnableLegacyProtect { get; set; }
|
||||
[Reactive] public string TunRouteExcludeAddress { get; set; }
|
||||
[Reactive] public string TunIPv4Address { get; set; }
|
||||
[Reactive] public string TunIPv6Address { get; set; }
|
||||
[Reactive] public partial bool TunAutoRoute { get; set; }
|
||||
[Reactive] public partial bool TunStrictRoute { get; set; }
|
||||
[Reactive] public partial string TunStack { get; set; }
|
||||
[Reactive] public partial int TunMtu { get; set; }
|
||||
[Reactive] public partial bool TunEnableIPv6Address { get; set; }
|
||||
[Reactive] public partial string TunIcmpRouting { get; set; }
|
||||
[Reactive] public partial bool TunEnableLegacyProtect { get; set; }
|
||||
[Reactive] public partial string TunRouteExcludeAddress { get; set; }
|
||||
[Reactive] public partial string TunIPv4Address { get; set; }
|
||||
[Reactive] public partial string TunIPv6Address { get; set; }
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
#region CoreType
|
||||
|
||||
[Reactive] public string CoreType1 { get; set; }
|
||||
[Reactive] public string CoreType2 { get; set; }
|
||||
[Reactive] public string CoreType3 { get; set; }
|
||||
[Reactive] public string CoreType4 { get; set; }
|
||||
[Reactive] public string CoreType5 { get; set; }
|
||||
[Reactive] public string CoreType6 { get; set; }
|
||||
[Reactive] public string CoreType7 { get; set; }
|
||||
[Reactive] public string CoreType9 { get; set; }
|
||||
[Reactive] public partial string CoreType1 { get; set; }
|
||||
[Reactive] public partial string CoreType2 { get; set; }
|
||||
[Reactive] public partial string CoreType3 { get; set; }
|
||||
[Reactive] public partial string CoreType4 { get; set; }
|
||||
[Reactive] public partial string CoreType5 { get; set; }
|
||||
[Reactive] public partial string CoreType6 { get; set; }
|
||||
[Reactive] public partial string CoreType7 { get; set; }
|
||||
[Reactive] public partial string CoreType9 { get; set; }
|
||||
|
||||
#endregion CoreType
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public OptionSettingViewModel()
|
||||
{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||
public partial class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
||||
|
||||
public Interaction<RxVoid, RxVoid> ProfilesFocusInteraction { get; } = new();
|
||||
|
||||
#region private prop
|
||||
|
||||
@@ -16,34 +16,34 @@ public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||
|
||||
#endregion private prop
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||
public BulkObservableCollection<ProfileItemModel> ProfileItems { get; } = [];
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ProfileItemModel SelectedProfile { get; set; }
|
||||
public partial ProfileItemModel SelectedProfile { get; set; }
|
||||
|
||||
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedSub { get; set; }
|
||||
public partial SubItem SelectedSub { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ServerFilter { get; set; }
|
||||
public partial string ServerFilter { get; set; }
|
||||
|
||||
// Include/Exclude filter for ConfigType
|
||||
[Reactive]
|
||||
public List<EConfigType> FilterConfigTypes { get; set; }
|
||||
public partial List<EConfigType> FilterConfigTypes { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool FilterExclude { get; set; }
|
||||
public partial bool FilterExclude { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool MultiSelect { get; set; }
|
||||
public partial bool MultiSelect { get; set; }
|
||||
|
||||
#endregion ObservableCollection
|
||||
|
||||
@@ -140,9 +140,9 @@ public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||
|
||||
try
|
||||
{
|
||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
||||
await ProfilesFocusInteraction.Handle(RxVoid.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ProfilesViewModel : MyReactiveObject
|
||||
public partial class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
public Interaction<ProfileItem, bool> SaveFileDialogInteraction { get; } = new();
|
||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
||||
public Interaction<string, Unit> ShareServerInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> DispatcherRefreshServersBizInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> AdjustMainLvColWidthInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<RxVoid, RxVoid> ProfilesFocusInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> ShareServerInteraction { get; } = new();
|
||||
public Interaction<RxVoid, RxVoid> DispatcherRefreshServersBizInteraction { get; } = new();
|
||||
public Interaction<RxVoid, RxVoid> AdjustMainLvColWidthInteraction { get; } = new();
|
||||
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
public EventChannel<Unit> RefreshServersRequested { get; } = new();
|
||||
public EventChannel<RxVoid> ReloadRequested { get; } = new();
|
||||
public EventChannel<RxVoid> RefreshServersRequested { get; } = new();
|
||||
|
||||
#region private prop
|
||||
|
||||
@@ -25,69 +25,69 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||
public BulkObservableCollection<ProfileItemModel> ProfileItems { get; } = [];
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public ProfileItemModel SelectedProfile { get; set; }
|
||||
public partial ProfileItemModel SelectedProfile { get; set; }
|
||||
|
||||
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedSub { get; set; }
|
||||
public partial SubItem SelectedSub { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedMoveToGroup { get; set; }
|
||||
public partial SubItem SelectedMoveToGroup { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ServerFilter { get; set; }
|
||||
public partial string ServerFilter { get; set; }
|
||||
|
||||
#endregion ObservableCollection
|
||||
|
||||
#region Menu
|
||||
|
||||
//servers delete
|
||||
public ReactiveCommand<Unit, Unit> EditServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> EditServerCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RemoveServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveDuplicateServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CopyServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SetDefaultServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ShareServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupAllServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> GenGroupRegionServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoveServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoveDuplicateServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> CopyServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SetDefaultServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ShareServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> GenGroupAllServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> GenGroupRegionServerCmd { get; }
|
||||
|
||||
//servers move
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveTopCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveUpCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveDownCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveBottomCmd { get; }
|
||||
public ReactiveCommand<SubItem, RxVoid> MoveToGroupCmd { get; }
|
||||
|
||||
//servers ping
|
||||
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MixedTestServerCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> TcpingServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RealPingServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> UdpTestServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SpeedServerCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SortServerResultCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveInvalidServerResultCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> FastRealPingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> TcpingServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RealPingServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> UdpTestServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SpeedServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SortServerResultCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RemoveInvalidServerResultCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> FastRealPingCmd { get; }
|
||||
|
||||
//servers export
|
||||
public ReactiveCommand<Unit, Unit> Export2ClientConfigCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> Export2ClientConfigCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> Export2ClientConfigClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> Export2ShareUrlCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> Export2ShareUrlBase64Cmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> Export2InnerUriCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> Export2ClientConfigClipboardCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> Export2ShareUrlCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> Export2ShareUrlBase64Cmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> Export2InnerUriCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddSubCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> EditSubCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteSubCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddSubCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> EditSubCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> DeleteSubCmd { get; }
|
||||
|
||||
#endregion Menu
|
||||
|
||||
@@ -347,9 +347,9 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
try
|
||||
{
|
||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
||||
await ProfilesFocusInteraction.Handle(RxVoid.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -397,9 +397,9 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
try
|
||||
{
|
||||
await DispatcherRefreshServersBizInteraction.Handle(Unit.Default);
|
||||
await DispatcherRefreshServersBizInteraction.Handle(RxVoid.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -419,7 +419,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
public async Task AdjustMainLvColWidth()
|
||||
{
|
||||
await AdjustMainLvColWidthInteraction.Handle(Unit.Default);
|
||||
await AdjustMainLvColWidthInteraction.Handle(RxVoid.Default);
|
||||
}
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
@@ -503,7 +503,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
var eConfigType = item.ConfigType;
|
||||
|
||||
bool? ret = false;
|
||||
if (eConfigType == EConfigType.Custom)
|
||||
if (eConfigType is EConfigType.Custom or EConfigType.Outbound)
|
||||
{
|
||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||
@@ -761,10 +761,9 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
_speedtestService ??= new SpeedtestService(_config, async (SpeedTestResult result) =>
|
||||
{
|
||||
RxSchedulers.MainThreadScheduler.Schedule(result, (scheduler, result) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = SetSpeedTestResult(result);
|
||||
return Disposable.Empty;
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
||||
public partial class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
@@ -8,25 +8,25 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
||||
public IList<string> InboundTagItems { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public RulesItem SelectedSource { get; set; }
|
||||
public partial RulesItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Domain { get; set; }
|
||||
public partial string Domain { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string IP { get; set; }
|
||||
public partial string IP { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Process { get; set; }
|
||||
public partial string Process { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string? RuleType { get; set; }
|
||||
public partial string? RuleType { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool AutoSort { get; set; }
|
||||
public partial bool AutoSort { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SelectProfileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SelectProfileCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public RoutingRuleDetailsViewModel(RulesItem rulesItem)
|
||||
{
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
||||
public partial class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> BrowseRulesFileInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> BrowseRulesFileInteraction { get; } = new();
|
||||
|
||||
private List<RulesItem> _rules;
|
||||
|
||||
[Reactive]
|
||||
public RoutingItem SelectedRouting { get; set; }
|
||||
public partial RoutingItem SelectedRouting { get; set; }
|
||||
|
||||
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
|
||||
public BulkObservableCollection<RulesItemModel> RulesItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public RulesItemModel SelectedSource { get; set; }
|
||||
public partial RulesItemModel SelectedSource { get; set; }
|
||||
|
||||
public IList<RulesItemModel> SelectedSources { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RuleAddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportRulesFromFileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportRulesFromClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportRulesFromUrlCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RuleRemoveCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RuleExportSelectedCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RuleAddCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportRulesFromFileCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportRulesFromClipboardCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ImportRulesFromUrlCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RuleRemoveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RuleExportSelectedCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveTopCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveUpCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveDownCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> MoveBottomCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public RoutingRuleSettingViewModel(RoutingItem routingItem)
|
||||
{
|
||||
@@ -48,7 +48,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
||||
});
|
||||
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var fileName = await BrowseRulesFileInteraction.Handle(Unit.Default);
|
||||
var fileName = await BrowseRulesFileInteraction.Handle(RxVoid.Default);
|
||||
await ImportRulesFromFileAsync(fileName);
|
||||
});
|
||||
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
@@ -277,7 +277,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
||||
var stringData = clipboardData;
|
||||
if (clipboardData == null)
|
||||
{
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(RxVoid.Default);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class RoutingSettingViewModel : MyReactiveObject
|
||||
public partial class RoutingSettingViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
|
||||
#region Reactive
|
||||
|
||||
public IObservableCollection<RoutingItemModel> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItemModel>();
|
||||
public BulkObservableCollection<RoutingItemModel> RoutingItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public RoutingItemModel SelectedSource { get; set; }
|
||||
public partial RoutingItemModel SelectedSource { get; set; }
|
||||
|
||||
public IList<RoutingItemModel> SelectedSources { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string DomainStrategy { get; set; }
|
||||
public partial string DomainStrategy { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string DomainStrategy4Singbox { get; set; }
|
||||
public partial string DomainStrategy4Singbox { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RoutingAdvancedAddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RoutingAdvancedRemoveCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RoutingAdvancedSetDefaultCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RoutingAdvancedImportRulesCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RoutingAdvancedAddCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RoutingAdvancedRemoveCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RoutingAdvancedSetDefaultCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> RoutingAdvancedImportRulesCmd { get; }
|
||||
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class StatusBarViewModel : MyReactiveObject
|
||||
public partial class StatusBarViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> PasswordInputInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> DispatcherRefreshIconInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<RxVoid, string?> PasswordInputInteraction { get; } = new();
|
||||
public Interaction<RxVoid, RxVoid> DispatcherRefreshIconInteraction { get; } = new();
|
||||
public EventChannel<bool> SubscriptionsUpdateRequested { get; } = new();
|
||||
public EventChannel<bool?> ShowHideWindowRequested { get; } = new();
|
||||
|
||||
@@ -12,94 +12,94 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
public static StatusBarViewModel Instance => _instance.Value;
|
||||
|
||||
public EventChannel<string> SetDefaultServerRequested { get; } = new();
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
public EventChannel<Unit> AddServerViaScanRequested { get; } = new();
|
||||
public EventChannel<Unit> AddServerViaClipboardRequested { get; } = new();
|
||||
public EventChannel<RxVoid> ReloadRequested { get; } = new();
|
||||
public EventChannel<RxVoid> AddServerViaScanRequested { get; } = new();
|
||||
public EventChannel<RxVoid> AddServerViaClipboardRequested { get; } = new();
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
|
||||
public BulkObservableCollection<RoutingItem> RoutingItems { get; } = [];
|
||||
|
||||
public IObservableCollection<ComboItem> Servers { get; } = new ObservableCollectionExtended<ComboItem>();
|
||||
public BulkObservableCollection<ComboItem> Servers { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public RoutingItem SelectedRouting { get; set; }
|
||||
public partial RoutingItem SelectedRouting { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ComboItem SelectedServer { get; set; }
|
||||
public partial ComboItem SelectedServer { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlServers { get; set; }
|
||||
public partial bool BlServers { get; set; }
|
||||
|
||||
#endregion ObservableCollection
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CopyProxyCmdToClipboardCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> NotifyLeftClickCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ShowWindowCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> HideWindowCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaClipboardCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddServerViaScanCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubUpdateViaProxyCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> CopyProxyCmdToClipboardCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> NotifyLeftClickCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> ShowWindowCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> HideWindowCmd { get; }
|
||||
|
||||
#region System Proxy
|
||||
|
||||
[Reactive]
|
||||
public bool BlSystemProxyClear { get; set; }
|
||||
public partial bool BlSystemProxyClear { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlSystemProxySet { get; set; }
|
||||
public partial bool BlSystemProxySet { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlSystemProxyNothing { get; set; }
|
||||
public partial bool BlSystemProxyNothing { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlSystemProxyPac { get; set; }
|
||||
public partial bool BlSystemProxyPac { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SystemProxyClearCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SystemProxySetCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SystemProxyNothingCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SystemProxyPacCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SystemProxyClearCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SystemProxySetCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SystemProxyNothingCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SystemProxyPacCmd { get; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlRouting { get; set; }
|
||||
public partial bool BlRouting { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public int SystemProxySelected { get; set; }
|
||||
public partial int SystemProxySelected { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlSystemProxyPacVisible { get; set; }
|
||||
public partial bool BlSystemProxyPacVisible { get; set; }
|
||||
|
||||
#endregion System Proxy
|
||||
|
||||
#region UI
|
||||
|
||||
[Reactive]
|
||||
public string InboundDisplay { get; set; }
|
||||
public partial string InboundDisplay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string InboundLanDisplay { get; set; }
|
||||
public partial string InboundLanDisplay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RunningServerDisplay { get; set; }
|
||||
public partial string RunningServerDisplay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RunningServerToolTipText { get; set; }
|
||||
public partial string RunningServerToolTipText { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string RunningInfoDisplay { get; set; }
|
||||
public partial string RunningInfoDisplay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SpeedProxyDisplay { get; set; }
|
||||
public partial string SpeedProxyDisplay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string SpeedDirectDisplay { get; set; }
|
||||
public partial string SpeedDirectDisplay { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool EnableTun { get; set; }
|
||||
public partial bool EnableTun { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool BlIsNonWindows { get; set; }
|
||||
public partial bool BlIsNonWindows { get; set; }
|
||||
|
||||
#endregion UI
|
||||
|
||||
@@ -349,10 +349,9 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
private async Task TestServerAvailabilitySub(string msg)
|
||||
{
|
||||
RxSchedulers.MainThreadScheduler.Schedule(msg, (scheduler, msg) =>
|
||||
RxSchedulers.MainThreadScheduler.Schedule(() =>
|
||||
{
|
||||
_ = TestServerAvailabilityResult(msg);
|
||||
return Disposable.Empty;
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
@@ -392,9 +391,9 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
{
|
||||
try
|
||||
{
|
||||
await DispatcherRefreshIconInteraction.Handle(Unit.Default);
|
||||
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
catch (UnhandledInteractionException<RxVoid, RxVoid>)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
@@ -433,7 +432,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||
ReloadRequested.Publish();
|
||||
await DispatcherRefreshIconInteraction.Handle(Unit.Default);
|
||||
await DispatcherRefreshIconInteraction.Handle(RxVoid.Default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +469,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
}
|
||||
else
|
||||
{
|
||||
var password = await PasswordInputInteraction.Handle(Unit.Default);
|
||||
var password = await PasswordInputInteraction.Handle(RxVoid.Default);
|
||||
if (password.IsNullOrEmpty())
|
||||
{
|
||||
_config.TunModeItem.EnableTun = false;
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class SubEditViewModel : MyReactiveObject, ICloseable
|
||||
public partial class SubEditViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedSource { get; set; }
|
||||
public partial SubItem SelectedSource { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SelectPrevProfileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SelectNextProfileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
[Reactive]
|
||||
public partial string CustomCoreType { get; set; }
|
||||
|
||||
public ReactiveCommand<RxVoid, RxVoid> SelectPrevProfileCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SelectNextProfileCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SaveCmd { get; }
|
||||
|
||||
public SubEditViewModel(SubItem subItem)
|
||||
{
|
||||
@@ -39,6 +42,7 @@ public class SubEditViewModel : MyReactiveObject, ICloseable
|
||||
});
|
||||
|
||||
SelectedSource = subItem.Id.IsNullOrEmpty() ? subItem : JsonUtils.DeepCopy(subItem);
|
||||
CustomCoreType = SelectedSource.CustomCoreType?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task SaveSubAsync()
|
||||
@@ -67,6 +71,8 @@ public class SubEditViewModel : MyReactiveObject, ICloseable
|
||||
}
|
||||
}
|
||||
|
||||
SelectedSource.CustomCoreType = Enum.TryParse<ECoreType>(CustomCoreType, out var coreType) ? coreType : null;
|
||||
|
||||
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class SubSettingViewModel : MyReactiveObject
|
||||
public partial class SubSettingViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
public Interaction<string, Unit> ShareSubInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> ShareSubInteraction { get; } = new();
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
public BulkObservableCollection<SubItem> SubItems { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedSource { get; set; }
|
||||
public partial SubItem SelectedSource { get; set; }
|
||||
|
||||
public IList<SubItem> SelectedSources { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SubAddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubDeleteCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubEditCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SubShareCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubAddCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubDeleteCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubEditCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> SubShareCmd { get; }
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
public SubSettingViewModel()
|
||||
|
||||
@@ -35,11 +35,16 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
|
||||
|
||||
var width = Math.Min(sizeItem.Width, workingArea.Width / scaling);
|
||||
var height = Math.Min(sizeItem.Height, workingArea.Height / scaling);
|
||||
var x = workingArea.X + ((workingArea.Width - (width * scaling)) / 2);
|
||||
var y = workingArea.Y + ((workingArea.Height - (height * scaling)) / 2);
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
var frameDiff = (FrameSize ?? ClientSize) - ClientSize;
|
||||
var totalWidth = (width + frameDiff.Width) * scaling;
|
||||
var totalHeight = (height + frameDiff.Height) * scaling;
|
||||
|
||||
var x = workingArea.X + ((workingArea.Width - totalWidth) / 2);
|
||||
var y = workingArea.Y + ((workingArea.Height - totalHeight) / 2);
|
||||
Position = new PixelPoint((int)x, (int)y);
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ReactiveUI />
|
||||
</Weavers>
|
||||
@@ -3,9 +3,6 @@ global using System.Collections.Generic;
|
||||
global using System.Globalization;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Reactive;
|
||||
global using System.Reactive.Disposables.Fluent;
|
||||
global using System.Reactive.Linq;
|
||||
global using System.Runtime.Versioning;
|
||||
global using System.Text;
|
||||
global using System.Threading;
|
||||
@@ -21,10 +18,11 @@ global using Avalonia.Media.Imaging;
|
||||
global using Avalonia.Platform;
|
||||
global using Avalonia.Styling;
|
||||
global using Avalonia.Threading;
|
||||
global using DynamicData;
|
||||
global using ReactiveUI;
|
||||
global using ReactiveUI.Avalonia;
|
||||
global using ReactiveUI.Fody.Helpers;
|
||||
global using ReactiveUI.Primitives;
|
||||
global using ReactiveUI.Primitives.Disposables;
|
||||
global using ReactiveUI.SourceGenerators;
|
||||
global using ServiceLib;
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
|
||||
@@ -5,13 +5,13 @@ using Semi.Avalonia;
|
||||
|
||||
namespace v2rayN.Desktop.ViewModels;
|
||||
|
||||
public class ThemeSettingViewModel : MyReactiveObject
|
||||
public partial class ThemeSettingViewModel : MyReactiveObject
|
||||
{
|
||||
[Reactive] public string CurrentTheme { get; set; }
|
||||
[Reactive] public partial string CurrentTheme { get; set; }
|
||||
|
||||
[Reactive] public int CurrentFontSize { get; set; }
|
||||
[Reactive] public partial int CurrentFontSize { get; set; }
|
||||
|
||||
[Reactive] public string CurrentLanguage { get; set; }
|
||||
[Reactive] public partial string CurrentLanguage { get; set; }
|
||||
|
||||
public ThemeSettingViewModel()
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
|
||||
@@ -103,57 +103,102 @@
|
||||
HorizontalAlignment="Left"
|
||||
MaxDropDownHeight="1000" />
|
||||
|
||||
<TextBlock
|
||||
<Separator
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbDisplayLog}" />
|
||||
<StackPanel
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Orientation="Horizontal">
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<Grid
|
||||
x:Name="gridCustomServer"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
ColumnDefinitions="Auto,Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbDisplayLog}" />
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Orientation="Horizontal">
|
||||
<ToggleSwitch
|
||||
x:Name="togDisplayLog"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TipDisplayLog}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPreSocksPort}" />
|
||||
<TextBox
|
||||
x:Name="txtPreSocksPort"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="{StaticResource Margin4}">
|
||||
<TextBlock
|
||||
Width="500"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TipPreSocksPort}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.CustomServerTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="gridCustomOutbound"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
ColumnDefinitions="Auto,Auto,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="Sing-Box Endpoint" />
|
||||
<ToggleSwitch
|
||||
x:Name="togDisplayLog"
|
||||
x:Name="togSingBoxEndpoint"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TipDisplayLog}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPreSocksPort}" />
|
||||
<TextBox
|
||||
x:Name="txtPreSocksPort"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
<StackPanel
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="{StaticResource Margin4}">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Width="500"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TipPreSocksPort}"
|
||||
Text="{x:Static resx:ResUI.TbCustomOutboundTip}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Width="500"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.CustomServerTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
|
||||
@@ -12,15 +12,19 @@ public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
|
||||
Loaded += Window_Loaded;
|
||||
btnCancel.Click += (s, e) => Close();
|
||||
|
||||
cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != nameof(ECoreType.v2rayN)).ToList().AppendEmpty();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Address, v => v.txtAddress.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.DisplayLog, v => v.togDisplayLog.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.IsSingboxEndpoint, v => v.togSingBoxEndpoint.IsChecked).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.BrowseServerCmd, v => v.btnBrowse).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.btnEdit).DisposeWith(disposables);
|
||||
@@ -34,6 +38,24 @@ public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeData(ProfileItem profileItem)
|
||||
{
|
||||
if (profileItem.ConfigType is EConfigType.Custom)
|
||||
{
|
||||
Title = ResUI.menuAddCustomServer;
|
||||
cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != nameof(ECoreType.v2rayN)).ToList();
|
||||
gridCustomServer.IsVisible = true;
|
||||
gridCustomOutbound.IsVisible = false;
|
||||
}
|
||||
else if (profileItem.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
Title = ResUI.menuAddCustomOutboundServer;
|
||||
cmbCoreType.ItemsSource = Global.CoreTypes;
|
||||
gridCustomServer.IsVisible = false;
|
||||
gridCustomOutbound.IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
txtRemarks.Focus();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Reactive.Disposables;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
@@ -39,11 +38,12 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
var configTypeBindings = new SerialDisposable().DisposeWith(disposables);
|
||||
var configTypeBindings = new SingleReplaceableDisposable();
|
||||
configTypeBindings.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
@@ -53,8 +53,8 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource.ConfigType)
|
||||
.Subscribe(configType =>
|
||||
{
|
||||
var currentTypeDisposables = new CompositeDisposable();
|
||||
configTypeBindings.Disposable = currentTypeDisposables;
|
||||
var currentTypeDisposables = new MultipleDisposable();
|
||||
configTypeBindings.Create(currentTypeDisposables);
|
||||
|
||||
switch (configType)
|
||||
{
|
||||
|
||||
@@ -33,9 +33,11 @@
|
||||
Header="{x:Static resx:ResUI.menuAddServerViaScan}"
|
||||
InputGesture="Ctrl+S" />
|
||||
<MenuItem x:Name="menuAddServerViaImage" Header="{x:Static resx:ResUI.menuAddServerViaImage}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuAddCustomServer" Header="{x:Static resx:ResUI.menuAddCustomServer}" />
|
||||
<MenuItem x:Name="menuAddPolicyGroupServer" Header="{x:Static resx:ResUI.menuAddPolicyGroupServer}" />
|
||||
<MenuItem x:Name="menuAddProxyChainServer" Header="{x:Static resx:ResUI.menuAddProxyChainServer}" />
|
||||
<MenuItem x:Name="menuAddCustomOutboundServer" Header="{x:Static resx:ResUI.menuAddCustomOutboundServer}" />
|
||||
<Separator />
|
||||
<MenuItem x:Name="menuAddVmessServer" Header="{x:Static resx:ResUI.menuAddVmessServer}" />
|
||||
<MenuItem x:Name="menuAddVlessServer" Header="{x:Static resx:ResUI.menuAddVlessServer}" />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using DialogHostAvalonia;
|
||||
using v2rayN.Desktop.Base;
|
||||
@@ -10,7 +9,7 @@ namespace v2rayN.Desktop.Views;
|
||||
public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
{
|
||||
private static Config _config;
|
||||
private readonly SerialDisposable _layoutBindingsDisposable = new();
|
||||
private readonly SingleReplaceableDisposable _layoutBindingsDisposable = new();
|
||||
private readonly WindowNotificationManager? _manager;
|
||||
private CheckUpdateView? _checkUpdateView;
|
||||
private BackupAndRestoreView? _backupAndRestoreView;
|
||||
@@ -48,6 +47,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddCustomOutboundServerCmd, v => v.menuAddCustomOutboundServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddProxyChainServerCmd, v => v.menuAddProxyChainServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
|
||||
@@ -111,7 +111,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
ViewModel.ShowHideWindowInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
ShowHideWindow(interaction.Input);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
AppEvents.SendSnackMsgRequested
|
||||
@@ -402,8 +402,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
|
||||
private void UpdateLayout(EGirdOrientation orientation)
|
||||
{
|
||||
var currentLayoutDisposables = new CompositeDisposable();
|
||||
_layoutBindingsDisposable.Disposable = currentLayoutDisposables;
|
||||
var currentLayoutDisposables = new MultipleDisposable();
|
||||
_layoutBindingsDisposable.Create(currentLayoutDisposables);
|
||||
|
||||
gridMain.IsVisible = orientation == EGirdOrientation.Horizontal;
|
||||
gridMain1.IsVisible = orientation == EGirdOrientation.Vertical;
|
||||
|
||||
@@ -21,8 +21,10 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
||||
var msg = interaction.Input;
|
||||
Dispatcher.UIThread.Post(() => ShowMsg(msg),
|
||||
DispatcherPriority.ApplicationIdle);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel?.FlushQueueMsg();
|
||||
});
|
||||
|
||||
TextEditorKeywordHighlighter.Attach(txtMsg, Global.LogLevelColors.ToDictionary(
|
||||
|
||||
@@ -109,8 +109,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||
this.Bind(ViewModel, vm => vm.UdpTestTarget, v => v.cmbUdpTestTarget.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.MixedConcurrencyCount, v => v.cmbMixedConcurrencyCount.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SubConvertUrl, v => v.cmbSubConvertUrl.Text).DisposeWith(disposables);
|
||||
this.Bind<OptionSettingViewModel, OptionSettingWindow, int, int>(ViewModel,
|
||||
vm => vm.MainGirdOrientation, view => view.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.MainGirdOrientation, view => view.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
|
||||
|
||||
@@ -35,7 +35,7 @@ public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
|
||||
ViewModel.ProfilesFocusInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
lstProfiles.Focus();
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Avalonia.VisualTree;
|
||||
using DialogHostAvalonia;
|
||||
using DynamicData.Binding;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
@@ -113,13 +112,13 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
||||
{
|
||||
var strData = interaction.Input;
|
||||
await AvaUtils.SetClipboardData(this, strData);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel.ProfilesFocusInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
lstProfiles.Focus();
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel.ShareServerInteraction.RegisterHandler(async interaction =>
|
||||
@@ -127,23 +126,23 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
||||
var url = interaction.Input;
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
return;
|
||||
}
|
||||
await ShareServer(url);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel.DispatcherRefreshServersBizInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(RefreshServersBiz, DispatcherPriority.Default);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel.AdjustMainLvColWidthInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
//AutofitColumnWidth();
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
AppEvents.AppExitRequested
|
||||
|
||||
@@ -22,7 +22,7 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingVie
|
||||
{
|
||||
var strData = interaction.Input;
|
||||
await AvaUtils.SetClipboardData(this, strData);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel.ReadTextFromClipboardInteraction.RegisterHandler(async interaction =>
|
||||
|
||||
@@ -34,7 +34,7 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
|
||||
{
|
||||
var strData = interaction.Input;
|
||||
await AvaUtils.SetClipboardData(this, strData);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel.PasswordInputInteraction.RegisterHandler(async interaction =>
|
||||
@@ -46,7 +46,7 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
|
||||
ViewModel.DispatcherRefreshIconInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(RefreshIcon, DispatcherPriority.Default);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</StackPanel>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
|
||||
<Grid ColumnDefinitions="Auto,400,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid ColumnDefinitions="Auto,400,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
@@ -69,8 +69,8 @@
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
PlaceholderText="{x:Static resx:ResUI.SubUrlTips}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.SubUrlTips}"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
@@ -100,8 +100,8 @@
|
||||
VerticalAlignment="Center"
|
||||
Classes="TextArea"
|
||||
MinLines="4"
|
||||
TextWrapping="Wrap"
|
||||
PlaceholderText="{x:Static resx:ResUI.SubUrlTips}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.SubUrlTips}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
@@ -179,8 +179,8 @@
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
PlaceholderText="{x:Static resx:ResUI.SubUrlTips}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.SubUrlTips}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="8"
|
||||
@@ -241,31 +241,44 @@
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPreSocksPort4Sub}" />
|
||||
<TextBox
|
||||
x:Name="txtPreSocksPort"
|
||||
Text="{x:Static resx:ResUI.LvCustomCoreType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbCustomCoreType"
|
||||
Grid.Row="11"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.TipPreSocksPort}"
|
||||
PlaceholderText="{x:Static resx:ResUI.TipPreSocksPort}" />
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="12"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPreSocksPort4Sub}" />
|
||||
<TextBox
|
||||
x:Name="txtPreSocksPort"
|
||||
Grid.Row="12"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
PlaceholderText="{x:Static resx:ResUI.TipPreSocksPort}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.TipPreSocksPort}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="13"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvMemo}" />
|
||||
<TextBox
|
||||
x:Name="txtMemo"
|
||||
Grid.Row="12"
|
||||
Grid.Row="13"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
|
||||
@@ -12,6 +12,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
||||
btnCancel.Click += (s, e) => Close();
|
||||
|
||||
cmbConvertTarget.ItemsSource = Global.SubConvertTargets;
|
||||
cmbCustomCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != nameof(ECoreType.v2rayN)).ToList().AppendEmpty();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
@@ -28,6 +29,7 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.NextProfile, v => v.txtNextProfile.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Memo, v => v.txtMemo.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CustomCoreType, v => v.cmbCustomCoreType.SelectedValue).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SelectPrevProfileCmd, v => v.btnSelectPrevProfile).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SelectNextProfileCmd, v => v.btnSelectNextProfile).DisposeWith(disposables);
|
||||
|
||||
@@ -46,11 +46,11 @@ public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
|
||||
var url = interaction.Input;
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
return;
|
||||
}
|
||||
await ShareSub(url);
|
||||
interaction.SetOutput(Unit.Default);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="AvaloniaUI.DiagnosticsSupport" />
|
||||
<PackageReference Include="DialogHost.Avalonia" />
|
||||
<PackageReference Include="ReactiveUI.Avalonia" />
|
||||
<PackageReference Include="ReactiveUI.SourceGenerators">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Semi.Avalonia" />
|
||||
<PackageReference Include="Semi.Avalonia.AvaloniaEdit" />
|
||||
<PackageReference Include="Semi.Avalonia.DataGrid">
|
||||
@@ -27,9 +31,6 @@
|
||||
<PackageReference Include="ReactiveUI">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ReactiveUI.Fody">
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -36,7 +36,12 @@ public class SimpleViewLocator : IViewLocator
|
||||
|
||||
public static SimpleViewLocator Instance => _instance.Value;
|
||||
|
||||
public IViewFor<TViewModel>? ResolveView<TViewModel>(string? contract = null) where TViewModel : class
|
||||
public IViewFor<TViewModel>? ResolveView<TViewModel>() where TViewModel : class
|
||||
{
|
||||
return ResolveView<TViewModel>(null);
|
||||
}
|
||||
|
||||
public IViewFor<TViewModel>? ResolveView<TViewModel>(string? contract) where TViewModel : class
|
||||
{
|
||||
if (_mappings.TryGetValue(typeof(TViewModel), out var factory))
|
||||
{
|
||||
@@ -45,7 +50,12 @@ public class SimpleViewLocator : IViewLocator
|
||||
return null;
|
||||
}
|
||||
|
||||
public IViewFor? ResolveView(object? instance, string? contract = null)
|
||||
public IViewFor? ResolveView(object? instance)
|
||||
{
|
||||
return ResolveView(instance, null);
|
||||
}
|
||||
|
||||
public IViewFor? ResolveView(object? instance, string? contract)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ReactiveUI />
|
||||
</Weavers>
|
||||
@@ -6,9 +6,6 @@ global using System.Diagnostics;
|
||||
global using System.Globalization;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Reactive;
|
||||
global using System.Reactive.Disposables.Fluent;
|
||||
global using System.Reactive.Linq;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Text;
|
||||
global using System.Threading;
|
||||
@@ -18,11 +15,11 @@ global using System.Windows.Data;
|
||||
global using System.Windows.Input;
|
||||
global using System.Windows.Interop;
|
||||
global using System.Windows.Threading;
|
||||
global using DynamicData;
|
||||
global using DynamicData.Binding;
|
||||
global using ReactiveUI;
|
||||
global using ReactiveUI.Builder;
|
||||
global using ReactiveUI.Fody.Helpers;
|
||||
global using ReactiveUI.Primitives;
|
||||
global using ReactiveUI.Primitives.Disposables;
|
||||
global using ReactiveUI.SourceGenerators;
|
||||
global using ServiceLib;
|
||||
global using ServiceLib.Base;
|
||||
global using ServiceLib.Common;
|
||||
|
||||
@@ -5,21 +5,20 @@ using Microsoft.Win32;
|
||||
|
||||
namespace v2rayN.ViewModels;
|
||||
|
||||
public class ThemeSettingViewModel : MyReactiveObject
|
||||
public partial class ThemeSettingViewModel : MyReactiveObject
|
||||
{
|
||||
private readonly PaletteHelper _paletteHelper = new();
|
||||
|
||||
private IObservableCollection<Swatch> _swatches = new ObservableCollectionExtended<Swatch>();
|
||||
public IObservableCollection<Swatch> Swatches => _swatches;
|
||||
public BulkObservableCollection<Swatch> Swatches { get; } = [];
|
||||
|
||||
[Reactive]
|
||||
public Swatch SelectedSwatch { get; set; }
|
||||
public partial Swatch SelectedSwatch { get; set; }
|
||||
|
||||
[Reactive] public string CurrentTheme { get; set; }
|
||||
[Reactive] public partial string CurrentTheme { get; set; }
|
||||
|
||||
[Reactive] public int CurrentFontSize { get; set; }
|
||||
[Reactive] public partial int CurrentFontSize { get; set; }
|
||||
|
||||
[Reactive] public string CurrentLanguage { get; set; }
|
||||
[Reactive] public partial string CurrentLanguage { get; set; }
|
||||
|
||||
public ThemeSettingViewModel()
|
||||
{
|
||||
@@ -47,10 +46,10 @@ public class ThemeSettingViewModel : MyReactiveObject
|
||||
|
||||
private void BindingUI()
|
||||
{
|
||||
_swatches.AddRange(new SwatchesProvider().Swatches);
|
||||
Swatches.AddRange(new SwatchesProvider().Swatches);
|
||||
if (!_config.UiItem.ColorPrimaryName.IsNullOrEmpty())
|
||||
{
|
||||
SelectedSwatch = _swatches.FirstOrDefault(t => t.Name == _config.UiItem.ColorPrimaryName);
|
||||
SelectedSwatch = Swatches.FirstOrDefault(t => t.Name == _config.UiItem.ColorPrimaryName);
|
||||
}
|
||||
CurrentTheme = _config.UiItem.CurrentTheme;
|
||||
CurrentFontSize = _config.UiItem.CurrentFontSize;
|
||||
|
||||
@@ -26,7 +26,7 @@ public partial class AddGroupServerWindow
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
|
||||
@@ -41,21 +41,116 @@
|
||||
materialDesign:ScrollViewerAssist.IsAutoHideEnabled="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource ModuleTitle}"
|
||||
Text="{x:Static resx:ResUI.menuServers}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbRemarks}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtRemarks"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
AcceptsReturn="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbAddress}" />
|
||||
<TextBox
|
||||
x:Name="txtAddress"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnBrowse"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
Content="{x:Static resx:ResUI.TbBrowse}"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnEdit"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
Content="{x:Static resx:ResUI.TbEdit}"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbCoreType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
MaxDropDownHeight="1000"
|
||||
Style="{StaticResource MyOutlinedTextComboBox}" />
|
||||
|
||||
<Separator
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Margin="0,2"
|
||||
Style="{DynamicResource MaterialDesignSeparator}" />
|
||||
|
||||
<Grid
|
||||
x:Name="gridCustomServer"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -67,87 +162,11 @@
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource ModuleTitle}"
|
||||
Text="{x:Static resx:ResUI.menuServers}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbRemarks}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtRemarks"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
AcceptsReturn="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbAddress}" />
|
||||
<TextBox
|
||||
x:Name="txtAddress"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnBrowse"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
Content="{x:Static resx:ResUI.TbBrowse}"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnEdit"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
Content="{x:Static resx:ResUI.TbEdit}"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbCoreType}" />
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
MaxDropDownHeight="1000"
|
||||
Style="{StaticResource MyOutlinedTextComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbDisplayLog}" />
|
||||
<StackPanel
|
||||
Grid.Row="4"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Orientation="Horizontal">
|
||||
@@ -160,7 +179,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
@@ -168,7 +187,7 @@
|
||||
Text="{x:Static resx:ResUI.TbPreSocksPort}" />
|
||||
<TextBox
|
||||
x:Name="txtPreSocksPort"
|
||||
Grid.Row="5"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
@@ -176,7 +195,7 @@
|
||||
AcceptsReturn="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
<StackPanel
|
||||
Grid.Row="6"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2">
|
||||
<TextBlock
|
||||
@@ -194,6 +213,48 @@
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="gridCustomOutbound"
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="Sing-Box Endpoint" />
|
||||
<ToggleButton
|
||||
x:Name="togSingBoxEndpoint"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Width="500"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbCustomOutboundTip}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
|
||||
@@ -12,11 +12,17 @@ public partial class AddServer2Window
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Address, v => v.txtAddress.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.DisplayLog, v => v.togDisplayLog.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.PreSocksPort, v => v.txtPreSocksPort.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.IsSingboxEndpoint, v => v.togSingBoxEndpoint.IsChecked).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.BrowseServerCmd, v => v.btnBrowse).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.btnEdit).DisposeWith(disposables);
|
||||
@@ -35,6 +41,24 @@ public partial class AddServer2Window
|
||||
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
|
||||
}
|
||||
|
||||
private void InitializeData(ProfileItem profileItem)
|
||||
{
|
||||
if (profileItem.ConfigType is EConfigType.Custom)
|
||||
{
|
||||
Title = ResUI.menuAddCustomServer;
|
||||
cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != nameof(ECoreType.v2rayN)).ToList();
|
||||
gridCustomServer.Visibility = Visibility.Visible;
|
||||
gridCustomOutbound.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else if (profileItem.ConfigType is EConfigType.Outbound)
|
||||
{
|
||||
Title = ResUI.menuAddCustomOutboundServer;
|
||||
cmbCoreType.ItemsSource = Global.CoreTypes;
|
||||
gridCustomServer.Visibility = Visibility.Collapsed;
|
||||
gridCustomOutbound.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
txtRemarks.Focus();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Reactive.Disposables;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace v2rayN.Views;
|
||||
@@ -37,11 +36,12 @@ public partial class AddServerWindow
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.KeepNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
var configTypeBindings = new SerialDisposable().DisposeWith(disposables);
|
||||
var configTypeBindings = new SingleReplaceableDisposable();
|
||||
configTypeBindings.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
@@ -51,8 +51,8 @@ public partial class AddServerWindow
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource.ConfigType)
|
||||
.Subscribe(configType =>
|
||||
{
|
||||
var currentTypeDisposables = new CompositeDisposable();
|
||||
configTypeBindings.Disposable = currentTypeDisposables;
|
||||
var currentTypeDisposables = new MultipleDisposable();
|
||||
configTypeBindings.Create(currentTypeDisposables);
|
||||
|
||||
switch (configType)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user