mirror of
https://github.com/2dust/v2rayN.git
synced 2026-10-11 15:54:59 +03:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1883178b5f | |||
| 1cde521d75 | |||
| 2407d44f4f | |||
| fbf822d74c | |||
| 4869a6cbba | |||
| b501efbc45 | |||
| 5ea8ae6420 | |||
| fa9d4a4769 | |||
| 771ef3192a | |||
| f5747bb321 | |||
| d099e44ff6 | |||
| c63d271d16 | |||
| 6152cab494 | |||
| 3187eeef79 | |||
| 3364ec0e57 | |||
| 23033fa002 | |||
| 125cbb18a1 | |||
| f85326cf0e | |||
| 3666fc549d | |||
| a147249f95 | |||
| a28f68c3a7 | |||
| 1a689f869c | |||
| 50023a5976 | |||
| c659ca8af8 | |||
| 7d6a967c18 | |||
| 6c2edcb382 | |||
| 086c991464 | |||
| a9da04acc2 | |||
| e0c790eeb2 | |||
| d433aa3b04 | |||
| 91a7ed65d5 | |||
| 984e685ab3 | |||
| 29f99ceacc | |||
| 93d8174dbe | |||
| fa1e201c76 | |||
| e1cb99cd6e |
@@ -676,6 +676,15 @@ EOF
|
||||
find "$stage/opt/v2rayN" -type f -exec chmod 0644 {} +
|
||||
[[ -f "$stage/opt/v2rayN/v2rayN" ]] && chmod 0755 "$stage/opt/v2rayN/v2rayN" || true
|
||||
|
||||
# Core binaries must be executable before installation: ordinary users cannot
|
||||
# chmod the root-owned files installed by dpkg.
|
||||
local core
|
||||
for core in xray/xray sing_box/sing-box mihomo/mihomo; do
|
||||
if [[ -f "$stage/opt/v2rayN/bin/$core" ]]; then
|
||||
chmod 0755 "$stage/opt/v2rayN/bin/$core"
|
||||
fi
|
||||
done
|
||||
|
||||
deb_out="$OUTPUT_DIR/v2rayn_${VERSION}_${deb_arch}.deb"
|
||||
dpkg-deb --root-owner-group --build "$stage" "$deb_out"
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ internal class UpgradeApp
|
||||
var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
|
||||
File.Delete(thisAppOldFile);
|
||||
var splitKey = "/";
|
||||
var allowedBaseDir = Utils.StartupPath();
|
||||
var pathComparison = Utils.GetPathComparison();
|
||||
|
||||
using var archive = ZipFile.OpenRead(fileName);
|
||||
foreach (var entry in archive.Entries)
|
||||
@@ -65,16 +67,21 @@ internal class UpgradeApp
|
||||
}
|
||||
|
||||
var fullName = string.Join(splitKey, lst[1..lst.Length]);
|
||||
var entryOutputPath = Utils.GetPath(fullName);
|
||||
if (!Utils.IsPathUnderDirectory(allowedBaseDir, entryOutputPath))
|
||||
{
|
||||
Console.WriteLine($"{Resx.Resource.FailedUpgrade} blocked potential path traversal: {entry.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(Utils.GetExePath(), Utils.GetPath(fullName), StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(Utils.GetExePath(), entryOutputPath, pathComparison))
|
||||
{
|
||||
File.Move(Utils.GetExePath(), thisAppOldFile);
|
||||
}
|
||||
|
||||
var entryOutputPath = Utils.GetPath(fullName);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
|
||||
//In the bin folder, if the file already exists, it will be skipped
|
||||
if (fullName.StartsWith("bin") && File.Exists(entryOutputPath))
|
||||
if (fullName.StartsWith("bin", pathComparison) && File.Exists(entryOutputPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,28 @@ internal class Utils
|
||||
return Path.Combine(startupPath, fileName);
|
||||
}
|
||||
|
||||
public static bool IsPathUnderDirectory(string baseDir, string targetPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(baseDir) || string.IsNullOrWhiteSpace(targetPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baseFull = Path.GetFullPath(baseDir);
|
||||
var targetFull = Path.GetFullPath(targetPath);
|
||||
|
||||
baseFull = baseFull.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
|
||||
return targetFull.StartsWith(baseFull, GetPathComparison());
|
||||
}
|
||||
|
||||
public static StringComparison GetPathComparison()
|
||||
{
|
||||
return OperatingSystem.IsWindows()
|
||||
? StringComparison.OrdinalIgnoreCase
|
||||
: StringComparison.Ordinal;
|
||||
}
|
||||
|
||||
public static string V2rayN => "v2rayN";
|
||||
|
||||
public static void StartV2RayN()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.25.2</Version>
|
||||
<Version>7.25.6</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.2" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.5" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.6" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.8" />
|
||||
<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="24.2.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="24.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.2.0" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="24.2.0" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
|
||||
@@ -27,11 +27,11 @@
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="TUnit" Version="1.68.17" />
|
||||
<PackageVersion Include="TUnit.Assertions.Should" Version="1.65.38-beta" />
|
||||
<PackageVersion Include="TUnit" Version="1.73.19" />
|
||||
<PackageVersion Include="TUnit.Assertions.Should" Version="1.72.16-beta" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace ServiceLib.Tests.Common;
|
||||
|
||||
public class UnixFileModeTests
|
||||
{
|
||||
[Test]
|
||||
public async Task AlreadyExecutableSystemBinary_ShouldNotRequireOwnership()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// /bin/sh is executable but owned by root on the Unix CI runners.
|
||||
// A regular user must not need chmod permission just to launch it.
|
||||
var mode = File.GetUnixFileMode("/bin/sh");
|
||||
await Utils.SetUnixFileMode("/bin/sh").Should().BeTrue();
|
||||
await File.GetUnixFileMode("/bin/sh").Should().BeEqualTo(mode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NonExecutableOwnedFile_ShouldGainExecuteBitsWithoutLosingOtherPermissions()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead;
|
||||
File.SetUnixFileMode(path, mode);
|
||||
|
||||
await Utils.SetUnixFileMode(path).Should().BeTrue();
|
||||
await File.GetUnixFileMode(path).Should().BeEqualTo(mode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,3 @@
|
||||
using System.Reflection;
|
||||
using ServiceLib.Enums;
|
||||
using ServiceLib.Manager;
|
||||
using ServiceLib.Models;
|
||||
|
||||
namespace ServiceLib.Tests.CoreConfig;
|
||||
|
||||
internal static class CoreConfigTestFactory
|
||||
|
||||
@@ -716,7 +716,6 @@ public class CoreConfigSingboxServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
|
||||
{
|
||||
|
||||
@@ -51,7 +51,7 @@ public class CoreConfigV2rayServiceTests
|
||||
.Select(item => item!.GetValue<string>())
|
||||
.Should().BeEquivalentTo(["a=1", "b=2"]);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildBalancer()
|
||||
{
|
||||
@@ -587,13 +587,11 @@ public class CoreConfigV2rayServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Arguments(true)]
|
||||
[Arguments(false)]
|
||||
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6(bool enableIPv6Address)
|
||||
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6()
|
||||
{
|
||||
// A host without a global IPv6 address has no IPv6 traffic that could bypass the tunnel,
|
||||
// while ::/0 would pull IPv6 attempts into a tunnel they cannot leave.
|
||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address);
|
||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address: false);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||
@@ -611,6 +609,26 @@ public class CoreConfigV2rayServiceTests
|
||||
await ipv6Routes.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GenerateClientConfigContent_Tun_ShouldRouteIPv6WhenEnabledWithoutGlobalIPv6()
|
||||
{
|
||||
// Hosts behind NAT66 hold only ULA addresses, so EnableIPv6Address must still route ::/0.
|
||||
var config = CoreConfigTestFactory.CreateConfigWithTun(ECoreType.Xray, enableIPv6Address: true);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
|
||||
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray, hasGlobalIPv6Address: false);
|
||||
|
||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||
|
||||
await result.Success.Should().BeTrue();
|
||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||
var tunInbound = cfg.inbounds.FirstOrDefault(i => i.protocol == "tun");
|
||||
|
||||
await tunInbound.Should().NotBeNull();
|
||||
await tunInbound!.settings.autoSystemRoutingTable.Should().Contain("::/0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using ServiceLib.Tests.CoreConfig;
|
||||
|
||||
namespace ServiceLib.Tests.Handler;
|
||||
|
||||
public class ConfigHandlerCustomSubscriptionTests
|
||||
{
|
||||
private const string FullXrayConfigs = """
|
||||
[
|
||||
{"remarks":"custom-1","inbounds":[{"tag":"socks","listen":"127.0.0.1","port":10808,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],"outbounds":[{"tag":"proxy","protocol":"vless","settings":{"vnext":[{"address":"127.0.0.1","port":443,"users":[{"id":"00000000-0000-0000-0000-000000000001","encryption":"none"}]}]},"streamSettings":{"network":"tcp"}}],"routing":{"domainStrategy":"AsIs","rules":[]}},
|
||||
{"remarks":"custom-2","inbounds":[{"tag":"socks","listen":"127.0.0.1","port":10808,"protocol":"socks","settings":{"auth":"noauth","udp":true}}],"outbounds":[{"tag":"proxy","protocol":"vless","settings":{"vnext":[{"address":"127.0.0.1","port":443,"users":[{"id":"00000000-0000-0000-0000-000000000001","encryption":"none"}]}]},"streamSettings":{"network":"tcp"}}],"routing":{"domainStrategy":"AsIs","rules":[]}}
|
||||
]
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task AddBatchServers_FullConfigsFromSubscription_InheritSubscriptionPreSocksPort()
|
||||
{
|
||||
var config = CoreConfigTestFactory.CreateConfig();
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
SQLiteHelper.Instance.CreateTable<SubItem>();
|
||||
SQLiteHelper.Instance.CreateTable<ProfileItem>();
|
||||
|
||||
var subId = $"sub-{Guid.NewGuid():N}";
|
||||
await SQLiteHelper.Instance.ReplaceAsync(new SubItem
|
||||
{
|
||||
Id = subId,
|
||||
Remarks = "sub",
|
||||
Url = "http://127.0.0.1/sub",
|
||||
PreSocksPort = 10808,
|
||||
});
|
||||
|
||||
var count = await ConfigHandler.AddBatchServers(config, FullXrayConfigs, subId, true);
|
||||
var profiles = await AppManager.Instance.ProfileItems(subId) ?? [];
|
||||
|
||||
await count.Should().BeEqualTo(2);
|
||||
await profiles.Should().HaveCount(2);
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
await profile.ConfigType.Should().BeEqualTo(EConfigType.Custom);
|
||||
await profile.PreSocksPort.Should().BeEqualTo(10808);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
namespace ServiceLib.Tests.Services;
|
||||
|
||||
public class SpeedtestServiceBatchTests
|
||||
{
|
||||
[Test]
|
||||
[Arguments(0)]
|
||||
[Arguments(1000)]
|
||||
public async Task EmptySelection_ShouldReturnNoBatches(int pageSize)
|
||||
{
|
||||
await GetBatches([], pageSize).Count.Should().BeEqualTo(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Arguments(0)]
|
||||
[Arguments(-1)]
|
||||
public async Task NonPositivePageSize_ShouldKeepEveryEligibleProfile(int pageSize)
|
||||
{
|
||||
var profiles = CreateProfiles();
|
||||
var batches = GetBatches(profiles, pageSize);
|
||||
|
||||
await batches.Count.Should().BeEqualTo(profiles.Count);
|
||||
await batches.SelectMany(batch => batch).Count().Should().BeEqualTo(profiles.Count);
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
await batches.SelectMany(batch => batch).Count(item => ReferenceEquals(item, profile)).Should().BeEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NormalBatches_ShouldRespectPageSizeAndKeepCoreTypesSeparate()
|
||||
{
|
||||
var profiles = CreateProfiles();
|
||||
var batches = GetBatches(profiles, 2);
|
||||
|
||||
await batches.Count.Should().BeEqualTo(3);
|
||||
await batches.SelectMany(batch => batch).Count().Should().BeEqualTo(profiles.Count);
|
||||
foreach (var batch in batches)
|
||||
{
|
||||
await (batch.Count > 0 && batch.Count <= 2).Should().BeTrue();
|
||||
await batch.Select(item => item.CoreType).Distinct().Count().Should().BeEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ServerTestItem> CreateProfiles() =>
|
||||
[
|
||||
new() { CoreType = ECoreType.Xray },
|
||||
new() { CoreType = ECoreType.Xray },
|
||||
new() { CoreType = ECoreType.Xray },
|
||||
new() { CoreType = ECoreType.sing_box },
|
||||
new() { CoreType = ECoreType.sing_box }
|
||||
];
|
||||
|
||||
private static List<List<ServerTestItem>> GetBatches(List<ServerTestItem> profiles, int pageSize)
|
||||
{
|
||||
var service = new SpeedtestService(new Config { SpeedTestItem = new() }, _ => Task.CompletedTask);
|
||||
var method = typeof(SpeedtestService).GetMethod("GetTestBatchItem", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
return (List<List<ServerTestItem>>)method.Invoke(service, [profiles, pageSize])!;
|
||||
}
|
||||
}
|
||||
@@ -43,9 +43,9 @@ public class McBeService : IUdpTest
|
||||
{
|
||||
// 0x1c | client alive time in ms (recorded from previous ping) |
|
||||
// server GUID | Magic | string length | Edition
|
||||
//
|
||||
//
|
||||
// Edition Example:
|
||||
//
|
||||
//
|
||||
// MCPE;Dedicated Server;527;1.19.1;0;10;13253860892328930865;Bedrock level;Survival;1;19132;19133;
|
||||
if (mcbeResponseBytes.Length < 48)
|
||||
{
|
||||
|
||||
@@ -501,6 +501,31 @@ public class Utils
|
||||
};
|
||||
}
|
||||
|
||||
public static List<(string, string)> ParseHeaders(string? headers)
|
||||
{
|
||||
var result = new List<(string, string)>();
|
||||
if (headers.IsNullOrEmpty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var lines = headers.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var parts = line.Split(new[] { ':' }, 2);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var key = parts[0].Trim();
|
||||
var value = parts[1].Trim();
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
result.Add((key, value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion Conversion Functions
|
||||
|
||||
#region Data Checks
|
||||
@@ -1416,7 +1441,11 @@ public class Utils
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
var currentMode = File.GetUnixFileMode(fileName);
|
||||
File.SetUnixFileMode(fileName, currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute);
|
||||
var executableMode = currentMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
|
||||
if (currentMode != executableMode)
|
||||
{
|
||||
File.SetUnixFileMode(fileName, executableMode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public enum EConfigType
|
||||
Anytls = 11,
|
||||
Naive = 12,
|
||||
Outbound = 13,
|
||||
MASQUE = 14,
|
||||
PolicyGroup = 101,
|
||||
ProxyChain = 102,
|
||||
}
|
||||
|
||||
@@ -261,7 +261,8 @@ public class Global
|
||||
{ EConfigType.TUIC, "tuic" },
|
||||
{ EConfigType.WireGuard, "wireguard" },
|
||||
{ EConfigType.Anytls, "anytls" },
|
||||
{ EConfigType.Naive, "naive" }
|
||||
{ EConfigType.Naive, "naive" },
|
||||
{ EConfigType.MASQUE, "masque" },
|
||||
};
|
||||
|
||||
public static readonly List<string> VmessSecurities =
|
||||
@@ -371,6 +372,7 @@ public class Global
|
||||
EConfigType.Trojan,
|
||||
EConfigType.Hysteria2,
|
||||
EConfigType.WireGuard,
|
||||
EConfigType.MASQUE,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
];
|
||||
@@ -386,6 +388,7 @@ public class Global
|
||||
EConfigType.Anytls,
|
||||
EConfigType.Naive,
|
||||
EConfigType.WireGuard,
|
||||
EConfigType.MASQUE,
|
||||
EConfigType.SOCKS,
|
||||
EConfigType.HTTP,
|
||||
];
|
||||
|
||||
@@ -49,6 +49,7 @@ public class CoreConfigContextBuilder
|
||||
RoutingItem = await ConfigHandler.GetDefaultRouting(config),
|
||||
IsWindows = Utils.IsWindows(),
|
||||
IsMacOS = Utils.IsMacOS(),
|
||||
IsLinux = Utils.IsLinux(),
|
||||
HasGlobalIPv6Address = Utils.HasGlobalIPv6Address(),
|
||||
ProtectCoreTypeList = config.TunModeItem.EnableTun ? [ECoreType.Xray, ECoreType.sing_box] : []
|
||||
};
|
||||
|
||||
@@ -292,6 +292,7 @@ public static class ConfigHandler
|
||||
EConfigType.WireGuard => await AddWireguardServer(config, item),
|
||||
EConfigType.Anytls => await AddAnytlsServer(config, item),
|
||||
EConfigType.Naive => await AddNaiveServer(config, item),
|
||||
EConfigType.MASQUE => await AddMasqueServer(config, item),
|
||||
_ => -1,
|
||||
};
|
||||
return ret;
|
||||
@@ -992,6 +993,29 @@ public static class ConfigHandler
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static async Task<int> AddMasqueServer(Config config, ProfileItem profileItem, bool toFile = true)
|
||||
{
|
||||
profileItem.ConfigType = EConfigType.MASQUE;
|
||||
|
||||
profileItem.Address = profileItem.Address.TrimEx();
|
||||
profileItem.Username = profileItem.Username.TrimEx();
|
||||
profileItem.Password = profileItem.Password.TrimEx();
|
||||
profileItem.Fingerprint = string.Empty;
|
||||
profileItem.Alpn = string.Empty;
|
||||
profileItem.Network = string.Empty;
|
||||
profileItem.AllowInsecure = string.Empty;
|
||||
if (profileItem.StreamSecurity.IsNullOrEmpty())
|
||||
{
|
||||
profileItem.StreamSecurity = Global.StreamSecurity;
|
||||
}
|
||||
if (profileItem.Password.IsNullOrEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
await AddServerCommon(config, profileItem, toFile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort the server list by the specified column
|
||||
/// Updates the sort order in the profile extension data
|
||||
@@ -1684,6 +1708,7 @@ public static class ConfigHandler
|
||||
EConfigType.WireGuard => await AddWireguardServer(config, profileItem, false),
|
||||
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
|
||||
EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
|
||||
EConfigType.MASQUE => await AddMasqueServer(config, profileItem, false),
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
@@ -1753,7 +1778,7 @@ public static class ConfigHandler
|
||||
}
|
||||
if (lstProfiles.Count > 0)
|
||||
{
|
||||
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub);
|
||||
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub, subItem);
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
@@ -1799,7 +1824,7 @@ public static class ConfigHandler
|
||||
|
||||
if (lstProfiles?.Count > 0)
|
||||
{
|
||||
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub);
|
||||
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub, subItem);
|
||||
if (count > 0)
|
||||
{
|
||||
return count;
|
||||
@@ -1813,7 +1838,8 @@ public static class ConfigHandler
|
||||
Config config,
|
||||
List<ProfileItem> lstProfiles,
|
||||
string subid,
|
||||
bool isSub)
|
||||
bool isSub,
|
||||
SubItem? subItem)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var it in lstProfiles)
|
||||
@@ -1823,6 +1849,7 @@ public static class ConfigHandler
|
||||
|
||||
if (it.ConfigType == EConfigType.Custom)
|
||||
{
|
||||
it.PreSocksPort = subItem?.PreSocksPort;
|
||||
if (await AddCustomServer(config, it, true) == 0)
|
||||
{
|
||||
count++;
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace ServiceLib.Handler.SysProxy;
|
||||
public static class SysProxyHandler
|
||||
{
|
||||
private static readonly string _tag = "SysProxyHandler";
|
||||
private static readonly Lazy<PacManager> _pacManager = new(() => new PacManager());
|
||||
|
||||
public static async Task<bool> UpdateSysProxy(Config config, bool forceDisable)
|
||||
{
|
||||
@@ -56,7 +57,7 @@ public static class SysProxyHandler
|
||||
|
||||
if (type != ESysProxyType.Pac && Utils.IsWindows())
|
||||
{
|
||||
PacManager.Instance.Stop();
|
||||
_pacManager.Value.Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -110,7 +111,7 @@ public static class SysProxyHandler
|
||||
private static async Task SetWindowsProxyPac(int port)
|
||||
{
|
||||
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
|
||||
await PacManager.Instance.StartAsync(port, portPac);
|
||||
await _pacManager.Value.StartAsync(port, portPac);
|
||||
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
|
||||
ProxySettingWindows.SetProxy(strProxy, "", 4);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class HttpClientHelper
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<string?> TryGetAsync(string url)
|
||||
public async Task<string?> TryGetAsync(string url, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (url.IsNullOrEmpty())
|
||||
{
|
||||
@@ -31,8 +31,12 @@ public class HttpClientHelper
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
var response = await httpClient.GetAsync(url, cancellationToken);
|
||||
return await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -75,15 +75,28 @@ public sealed class SQLiteHelper
|
||||
|
||||
public async Task DisposeDbConnectionAsync()
|
||||
{
|
||||
await Task.Factory.StartNew(() =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_db?.Close();
|
||||
_db?.Dispose();
|
||||
_db = null;
|
||||
try
|
||||
{
|
||||
_db?.Close();
|
||||
_db?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db = null;
|
||||
}
|
||||
|
||||
_dbAsync?.GetConnection()?.Close();
|
||||
_dbAsync?.GetConnection()?.Dispose();
|
||||
_dbAsync = null;
|
||||
try
|
||||
{
|
||||
var conn = _dbAsync?.GetConnection();
|
||||
conn?.Close();
|
||||
conn?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dbAsync = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ public sealed class CoreInfoManager
|
||||
DownloadUrlLinuxLoong64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-loong64.tar.gz",
|
||||
DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz",
|
||||
DownloadUrlOSXArm64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-arm64.tar.gz",
|
||||
LockedMaxVersion = new SemanticVersion(1, 14, int.MaxValue),
|
||||
//LockedMaxVersion = new SemanticVersion(1, 14, int.MaxValue),
|
||||
Match = "sing-box",
|
||||
VersionArg = "version",
|
||||
},
|
||||
|
||||
@@ -2,36 +2,37 @@ namespace ServiceLib.Manager;
|
||||
|
||||
public class PacManager
|
||||
{
|
||||
private static readonly Lazy<PacManager> _instance = new(() => new PacManager());
|
||||
public static PacManager Instance => _instance.Value;
|
||||
|
||||
private int _httpPort;
|
||||
private const string Tag = "PacManager";
|
||||
private CancellationTokenSource? _cts;
|
||||
private int _pacPort;
|
||||
private TcpListener? _tcpListener;
|
||||
private byte[] _writeContent;
|
||||
private bool _isRunning;
|
||||
private bool _needRestart = true;
|
||||
private byte[] _writeContent = [];
|
||||
|
||||
public async Task StartAsync(int httpPort, int pacPort)
|
||||
{
|
||||
_needRestart = httpPort != _httpPort || pacPort != _pacPort || !_isRunning;
|
||||
var content = await InitText(httpPort);
|
||||
_writeContent = content;
|
||||
|
||||
_httpPort = httpPort;
|
||||
_pacPort = pacPort;
|
||||
|
||||
await InitText();
|
||||
|
||||
if (_needRestart)
|
||||
if (_tcpListener is not null && _pacPort == pacPort)
|
||||
{
|
||||
Stop();
|
||||
RunListener();
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
var cts = new CancellationTokenSource();
|
||||
var listener = TcpListener.Create(pacPort);
|
||||
listener.Start();
|
||||
|
||||
_cts = cts;
|
||||
_pacPort = pacPort;
|
||||
_tcpListener = listener;
|
||||
_ = ListenLoopAsync(listener, cts.Token);
|
||||
}
|
||||
|
||||
private async Task InitText()
|
||||
private async Task<byte[]> InitText(int httpPort)
|
||||
{
|
||||
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath;
|
||||
var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath))
|
||||
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem.CustomSystemProxyPacPath;
|
||||
var fileName = customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath)
|
||||
? customSystemProxyPacPath
|
||||
: Path.Combine(Utils.GetConfigPath(), "pac.txt");
|
||||
|
||||
@@ -45,7 +46,7 @@ public class PacManager
|
||||
}
|
||||
|
||||
var pacText = await File.ReadAllTextAsync(fileName);
|
||||
pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{_httpPort};DIRECT;");
|
||||
pacText = pacText.Replace("__PROXY__", $"PROXY 127.0.0.1:{httpPort};DIRECT;");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("HTTP/1.0 200 OK");
|
||||
@@ -54,59 +55,43 @@ public class PacManager
|
||||
sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText));
|
||||
sb.AppendLine();
|
||||
sb.Append(pacText);
|
||||
_writeContent = Encoding.UTF8.GetBytes(sb.ToString());
|
||||
return Encoding.UTF8.GetBytes(sb.ToString());
|
||||
}
|
||||
|
||||
private void RunListener()
|
||||
private async Task ListenLoopAsync(TcpListener listener, CancellationToken token)
|
||||
{
|
||||
_tcpListener = TcpListener.Create(_pacPort);
|
||||
_isRunning = true;
|
||||
_tcpListener.Start();
|
||||
Task.Factory.StartNew(async () =>
|
||||
var buffer = new byte[1024];
|
||||
try
|
||||
{
|
||||
while (_isRunning)
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_tcpListener.Pending())
|
||||
{
|
||||
await Task.Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
var client = await _tcpListener.AcceptTcpClientAsync();
|
||||
await Task.Run(() => WriteContent(client));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
using var client = await listener.AcceptTcpClientAsync(token).ConfigureAwait(false);
|
||||
await using var stream = client.GetStream();
|
||||
_ = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
await stream.WriteAsync(_writeContent, token).ConfigureAwait(false);
|
||||
await stream.FlushAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
private void WriteContent(TcpClient client)
|
||||
{
|
||||
var stream = client.GetStream();
|
||||
stream.Write(_writeContent, 0, _writeContent.Length);
|
||||
stream.Flush();
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(Tag, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
listener.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_tcpListener == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_isRunning = false;
|
||||
_tcpListener.Stop();
|
||||
_tcpListener = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
_cts?.Cancel();
|
||||
_tcpListener?.Stop();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
_pacPort = 0;
|
||||
_tcpListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,17 @@ public class TaskManager
|
||||
_config = config;
|
||||
_updateFunc = updateFunc;
|
||||
|
||||
_ = Task.Factory.StartNew(
|
||||
ScheduledTasks,
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
Task.Run(ScheduledTasks);
|
||||
}
|
||||
|
||||
private async Task ScheduledTasks()
|
||||
{
|
||||
Logging.SaveLog("Setup Scheduled Tasks");
|
||||
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
||||
var numOfExecuted = 1;
|
||||
while (true)
|
||||
while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
|
||||
{
|
||||
//1 minute
|
||||
await Task.Delay(1000 * 60);
|
||||
|
||||
//Execute once 1 minute
|
||||
try
|
||||
{
|
||||
@@ -140,7 +134,7 @@ public class TaskManager
|
||||
|
||||
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask);
|
||||
|
||||
var msgs = await updateService.CheckHasUpdateOnlyAll(_config.CheckUpdateItem.CheckPreReleaseUpdate, _config.CheckUpdateItem.UpdateViaProxy);
|
||||
var msgs = await updateService.CheckHasUpdateOnlyAll(_config.CheckUpdateItem.UpdateViaProxy);
|
||||
foreach (var msg in msgs)
|
||||
{
|
||||
await _updateFunc?.Invoke(false, msg);
|
||||
|
||||
@@ -242,9 +242,9 @@ public class WebDavItem
|
||||
[Serializable]
|
||||
public class CheckUpdateItem
|
||||
{
|
||||
public bool CheckPreReleaseUpdate { get; set; }
|
||||
public bool UpdateViaProxy { get; set; } = true;
|
||||
public List<string>? SelectedCoreTypes { get; set; }
|
||||
public List<string>? CheckPreReleaseCoreTypes { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
||||
@@ -24,6 +24,7 @@ public record CoreConfigContext
|
||||
|
||||
public bool IsWindows { get; init; }
|
||||
public bool IsMacOS { get; init; }
|
||||
public bool IsLinux { get; init; }
|
||||
|
||||
// Defaults to true so that a context built without this flag keeps routing IPv6 into the
|
||||
// tunnel; only a positive detection of the host having no global IPv6 address turns it off.
|
||||
|
||||
@@ -128,8 +128,6 @@ public class User4Sbox
|
||||
|
||||
public class Outbound4Sbox : BaseServer4Sbox
|
||||
{
|
||||
public string? server { get; set; }
|
||||
public int? server_port { get; set; }
|
||||
public List<string>? server_ports { get; set; }
|
||||
public string? uuid { get; set; }
|
||||
public string? security { get; set; }
|
||||
@@ -171,6 +169,10 @@ public class Endpoints4Sbox : BaseServer4Sbox
|
||||
public string? udp_timeout { get; set; }
|
||||
public int? workers { get; set; }
|
||||
public List<Peer4Sbox> peers { get; set; }
|
||||
public string? username { get; set; }
|
||||
public string? password { get; set; }
|
||||
public string? path { get; set; }
|
||||
public Dictionary<string, List<string>>? headers { get; set; }
|
||||
}
|
||||
|
||||
public class Peer4Sbox
|
||||
@@ -271,10 +273,8 @@ public class Server4Sbox : BaseServer4Sbox
|
||||
public string? inet4_range { get; set; }
|
||||
public string? inet6_range { get; set; }
|
||||
public string? client_subnet { get; set; }
|
||||
public string? server { get; set; }
|
||||
public new string? domain_resolver { get; set; }
|
||||
[JsonPropertyName("interface")] public string? Interface { get; set; }
|
||||
public int? server_port { get; set; }
|
||||
public string? path { get; set; }
|
||||
public Headers4Sbox? headers { get; set; }
|
||||
|
||||
@@ -359,4 +359,6 @@ public abstract class BaseServer4Sbox : DialFields4Sbox
|
||||
{
|
||||
public string type { get; set; }
|
||||
public string tag { get; set; }
|
||||
public string? server { get; set; }
|
||||
public int? server_port { get; set; }
|
||||
}
|
||||
|
||||
@@ -92,6 +92,10 @@ public class Inboundsettings4Ray
|
||||
public string? autoOutboundsInterface { get; set; }
|
||||
|
||||
public List<string>? dns { get; set; }
|
||||
|
||||
public bool? autoSystemDnsToGateway { get; set; }
|
||||
|
||||
public List<string>? autoSystemWfpBlockLeak { get; set; }
|
||||
}
|
||||
|
||||
public class Sniffing4Ray
|
||||
@@ -315,6 +319,7 @@ public class StreamSettings4Ray
|
||||
public GrpcSettings4Ray? grpcSettings { get; set; }
|
||||
|
||||
public HysteriaSettings4Ray? hysteriaSettings { get; set; }
|
||||
public MasqueSettings4Ray? masqueSettings { get; set; }
|
||||
|
||||
public object? finalmask { get; set; }
|
||||
|
||||
@@ -443,6 +448,12 @@ public class HysteriaSettings4Ray
|
||||
public string? auth { get; set; }
|
||||
}
|
||||
|
||||
public class MasqueSettings4Ray
|
||||
{
|
||||
public string? path { get; set; }
|
||||
public Dictionary<string, string>? headers { get; set; }
|
||||
}
|
||||
|
||||
public class UdpHop4Ray
|
||||
{
|
||||
public string? ports { get; set; }
|
||||
|
||||
@@ -3,6 +3,8 @@ namespace ServiceLib.Models.Dto;
|
||||
public partial class CheckUpdateModel : ReactiveObject
|
||||
{
|
||||
public bool? IsSelected { get; set; }
|
||||
public bool IsCheckPreRelease { get; set; }
|
||||
public bool ShowCheckPreRelease { get; set; }
|
||||
public ECoreType? CoreType { get; set; }
|
||||
[Reactive] public partial string? Remarks { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
|
||||
@@ -46,6 +46,10 @@ public record ProtocolExtraItem
|
||||
public int? InsecureConcurrency { get; init; }
|
||||
public bool? NaiveQuic { get; init; }
|
||||
|
||||
// MASQUE
|
||||
public string? MasquePath { get; init; }
|
||||
public string? MasqueHeaders { get; init; }
|
||||
|
||||
// group profile
|
||||
public string? GroupType { get; init; }
|
||||
public string? ChildItems { get; init; }
|
||||
|
||||
Generated
+19
-1
@@ -789,6 +789,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add [MASQUE] 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuAddMasqueServer {
|
||||
get {
|
||||
return ResourceManager.GetString("menuAddMasqueServer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Add [NaïveProxy] 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -924,6 +933,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Check and Update 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string menuCheckAndUpdate {
|
||||
get {
|
||||
return ResourceManager.GetString("menuCheckAndUpdate", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Only Check 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2149,7 +2167,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Not Support 的本地化字符串。
|
||||
/// 查找类似 Not Supported 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string MsgNotSupport {
|
||||
get {
|
||||
|
||||
@@ -399,6 +399,9 @@
|
||||
<data name="MsgServerTitle" xml:space="preserve">
|
||||
<value>Filter, press Enter to execute</value>
|
||||
</data>
|
||||
<data name="menuCheckAndUpdate" xml:space="preserve">
|
||||
<value>Check and Update</value>
|
||||
</data>
|
||||
<data name="menuCheckUpdate" xml:space="preserve">
|
||||
<value>Check Update</value>
|
||||
</data>
|
||||
@@ -1899,4 +1902,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbSortingProcess" xml:space="preserve">
|
||||
<value>Process</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="menuAddMasqueServer" xml:space="preserve">
|
||||
<value>Add [MASQUE]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -399,8 +399,11 @@
|
||||
<data name="MsgServerTitle" xml:space="preserve">
|
||||
<value>Фильтр, нажмите Enter для выполнения</value>
|
||||
</data>
|
||||
<data name="menuCheckAndUpdate" xml:space="preserve">
|
||||
<value>Проверить и обновить</value>
|
||||
</data>
|
||||
<data name="menuCheckUpdate" xml:space="preserve">
|
||||
<value>Обновить</value>
|
||||
<value>Проверить обновления</value>
|
||||
</data>
|
||||
<data name="menuClose" xml:space="preserve">
|
||||
<value>Закрыть</value>
|
||||
@@ -1780,7 +1783,7 @@
|
||||
<value>Доступна новая версия {0}: {1}</value>
|
||||
</data>
|
||||
<data name="menuCheckOnly" xml:space="preserve">
|
||||
<value>Проверить</value>
|
||||
<value>Только проверить</value>
|
||||
</data>
|
||||
<data name="MsgNotSupport" xml:space="preserve">
|
||||
<value>Не поддерживается</value>
|
||||
@@ -1899,4 +1902,7 @@
|
||||
<data name="TbSortingProcess" xml:space="preserve">
|
||||
<value>Процесс</value>
|
||||
</data>
|
||||
<data name="menuAddMasqueServer" xml:space="preserve">
|
||||
<value>Добавить сервер [MASQUE]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -399,6 +399,9 @@
|
||||
<data name="MsgServerTitle" xml:space="preserve">
|
||||
<value>过滤器,按回车执行</value>
|
||||
</data>
|
||||
<data name="menuCheckAndUpdate" xml:space="preserve">
|
||||
<value>检查并更新</value>
|
||||
</data>
|
||||
<data name="menuCheckUpdate" xml:space="preserve">
|
||||
<value>检查更新</value>
|
||||
</data>
|
||||
@@ -1891,4 +1894,7 @@
|
||||
<data name="TbSortingProcess" xml:space="preserve">
|
||||
<value>进程</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="menuAddMasqueServer" xml:space="preserve">
|
||||
<value>添加 [MASQUE]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -399,6 +399,9 @@
|
||||
<data name="MsgServerTitle" xml:space="preserve">
|
||||
<value>過濾器,按 Enter 執行</value>
|
||||
</data>
|
||||
<data name="menuCheckAndUpdate" xml:space="preserve">
|
||||
<value>檢查並更新</value>
|
||||
</data>
|
||||
<data name="menuCheckUpdate" xml:space="preserve">
|
||||
<value>檢查更新</value>
|
||||
</data>
|
||||
|
||||
@@ -300,40 +300,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
|
||||
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
|
||||
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
|
||||
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
|
||||
Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
|
||||
IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
|
||||
SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
|
||||
SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
|
||||
ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
|
||||
DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
|
||||
TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
|
||||
fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
|
||||
sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
|
||||
WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
|
||||
nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
|
||||
dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
|
||||
NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
|
||||
AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
|
||||
MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
|
||||
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
|
||||
uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
|
||||
PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
|
||||
JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
|
||||
gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
|
||||
j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
|
||||
5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
|
||||
o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
|
||||
/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
|
||||
Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
|
||||
W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
|
||||
hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
|
||||
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
|
||||
@@ -732,20 +698,6 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
|
||||
4uJEvlz36hz1
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
|
||||
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
|
||||
A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
|
||||
WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
|
||||
IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
|
||||
AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
|
||||
jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
|
||||
4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
|
||||
BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
|
||||
VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
|
||||
ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
@@ -805,38 +757,6 @@ HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
|
||||
bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
|
||||
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
|
||||
MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
|
||||
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
|
||||
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
|
||||
A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
|
||||
nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
|
||||
6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
|
||||
MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
|
||||
RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
|
||||
f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
|
||||
+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
|
||||
dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
|
||||
Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
|
||||
G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
|
||||
gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
|
||||
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
|
||||
FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
|
||||
vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
|
||||
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
|
||||
B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
|
||||
NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
|
||||
yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
|
||||
HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
|
||||
xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
|
||||
TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
|
||||
JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
|
||||
7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
|
||||
6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
|
||||
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
|
||||
@@ -1189,31 +1109,6 @@ AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
|
||||
RA+GsCyRxj3qrg+E
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
|
||||
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
|
||||
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
|
||||
cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
|
||||
dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
|
||||
pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
|
||||
b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
|
||||
aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
|
||||
IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
|
||||
lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
|
||||
AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
|
||||
VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
|
||||
ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
|
||||
BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
|
||||
AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
|
||||
U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
|
||||
bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
|
||||
+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
|
||||
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
|
||||
uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
|
||||
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
|
||||
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
|
||||
|
||||
@@ -365,9 +365,16 @@ public partial class CoreConfigSingboxService
|
||||
rule4ExpectedIPs = JsonUtils.DeepCopy(rule);
|
||||
rule4ExpectedIPs.geosite = regionGeosite;
|
||||
}
|
||||
if (rule.geosite?.Count > 0
|
||||
|| rule.domain?.Count > 0
|
||||
|| rule.domain_keyword?.Count > 0
|
||||
|| rule.domain_regex?.Count > 0
|
||||
|| rule.domain_suffix?.Count > 0)
|
||||
{
|
||||
AddRules(rule, item, directDnsList);
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.geosite?.Count > 0 || rule.domain?.Count > 0)
|
||||
else
|
||||
{
|
||||
AddRules(rule, item, directDnsList);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
|
||||
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { };
|
||||
tunInbound.interface_name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "singbox_tun";
|
||||
tunInbound.interface_name = context.IsMacOS ? null : "singbox_tun";
|
||||
tunInbound.mtu = _config.TunModeItem.Mtu;
|
||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||
|
||||
@@ -77,7 +77,7 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
if (_node.ConfigType == EConfigType.WireGuard)
|
||||
if (_node.ConfigType is EConfigType.WireGuard or EConfigType.MASQUE)
|
||||
{
|
||||
var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
|
||||
FillEndpoint(endpoint);
|
||||
@@ -350,13 +350,13 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
var protocolExtra = _node.GetProtocolExtra();
|
||||
|
||||
endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"];
|
||||
endpoint.type = Global.ProtocolTypes[_node.ConfigType];
|
||||
|
||||
switch (_node.ConfigType)
|
||||
{
|
||||
case EConfigType.WireGuard:
|
||||
{
|
||||
endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"];
|
||||
var peer = new Peer4Sbox
|
||||
{
|
||||
public_key = protocolExtra.WgPublicKey ?? string.Empty,
|
||||
@@ -371,6 +371,28 @@ public partial class CoreConfigSingboxService
|
||||
endpoint.peers = [peer];
|
||||
break;
|
||||
}
|
||||
case EConfigType.MASQUE:
|
||||
{
|
||||
endpoint.type = "masque-client";
|
||||
endpoint.server = _node.Address;
|
||||
endpoint.server_port = _node.Port;
|
||||
endpoint.username = _node.Username;
|
||||
endpoint.password = _node.Password;
|
||||
endpoint.path = protocolExtra?.MasquePath.NullIfEmpty();
|
||||
if (!string.IsNullOrEmpty(protocolExtra?.MasqueHeaders))
|
||||
{
|
||||
var headers = Utils.ParseHeaders(protocolExtra.MasqueHeaders)
|
||||
.GroupBy(x => x.Item1, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Select(x => x.Item2).ToList(),
|
||||
StringComparer.OrdinalIgnoreCase
|
||||
);
|
||||
endpoint.headers = headers.Count > 0 ? headers : null;
|
||||
}
|
||||
FillOutboundTls(endpoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -402,7 +424,7 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
private void FillOutboundTls(Outbound4Sbox outbound)
|
||||
private void FillOutboundTls(BaseServer4Sbox server)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -476,7 +498,7 @@ public partial class CoreConfigSingboxService
|
||||
{
|
||||
tls.ech = ech;
|
||||
}
|
||||
outbound.tls = tls;
|
||||
server.tls = tls;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -67,12 +67,10 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
|
||||
tunInbound.settings.gateway = [address];
|
||||
// Route both families into the tunnel regardless of EnableIPv6Address. That option only
|
||||
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing
|
||||
// table makes IPv6 follow the system default route and bypass the tunnel entirely.
|
||||
// A host without a global IPv6 address is the exception: it has nothing to leak,
|
||||
// and IPv6 sent into the tunnel would have no way back out.
|
||||
tunInbound.settings.autoSystemRoutingTable = context.HasGlobalIPv6Address
|
||||
// Without ::/0, IPv6 bypasses the tunnel. Skip it only when the host has no global IPv6
|
||||
// address and EnableIPv6Address is off; NAT66 hosts have only ULA addresses.
|
||||
var routeIPv6 = _config.TunModeItem.EnableIPv6Address || context.HasGlobalIPv6Address;
|
||||
tunInbound.settings.autoSystemRoutingTable = routeIPv6
|
||||
? ["0.0.0.0/0", "::/0"]
|
||||
: ["0.0.0.0/0"];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
@@ -90,6 +88,15 @@ public partial class CoreConfigV2rayService
|
||||
// tunInbound.sniffing.routeOnly = inbound.sniffing.routeOnly;
|
||||
tunInbound.sniffing.routeOnly = true;
|
||||
|
||||
if (context.IsWindows && _config.TunModeItem.StrictRoute == true)
|
||||
{
|
||||
tunInbound.settings.autoSystemWfpBlockLeak = ["dns", "misconfigtun"];
|
||||
}
|
||||
if (context.IsLinux)
|
||||
{
|
||||
tunInbound.settings.autoSystemDnsToGateway = true;
|
||||
}
|
||||
|
||||
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
var wholeInternet = IPNetwork2.Parse("0.0.0.0/0");
|
||||
@@ -99,7 +106,7 @@ public partial class CoreConfigV2rayService
|
||||
.Where(x => x != null).ToList();
|
||||
|
||||
var includeList = new List<IPNetwork2> { wholeInternet };
|
||||
var includeListV6 = context.HasGlobalIPv6Address
|
||||
var includeListV6 = routeIPv6
|
||||
? new List<IPNetwork2> { wholeInternetV6 }
|
||||
: new List<IPNetwork2>();
|
||||
|
||||
|
||||
@@ -182,6 +182,12 @@ public partial class CoreConfigV2rayService
|
||||
outboundSettings.version = 2;
|
||||
break;
|
||||
}
|
||||
case EConfigType.MASQUE:
|
||||
{
|
||||
outboundSettings.address = _node.Address;
|
||||
outboundSettings.port = _node.Port;
|
||||
break;
|
||||
}
|
||||
case EConfigType.WireGuard:
|
||||
{
|
||||
var address = _node.Address;
|
||||
@@ -257,8 +263,13 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
network = "hysteria";
|
||||
}
|
||||
else if (_node.ConfigType == EConfigType.MASQUE)
|
||||
{
|
||||
network = "masque";
|
||||
}
|
||||
streamSettings.network = network;
|
||||
var transport = _node.GetTransportExtra();
|
||||
var protocolExtra = _node.GetProtocolExtra();
|
||||
var host = string.Empty;
|
||||
var path = string.Empty;
|
||||
var kcpSeed = string.Empty;
|
||||
@@ -502,7 +513,6 @@ public partial class CoreConfigV2rayService
|
||||
break;
|
||||
|
||||
case "hysteria":
|
||||
var protocolExtra = _node.GetProtocolExtra();
|
||||
var ports = protocolExtra?.Ports;
|
||||
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
|
||||
? su
|
||||
@@ -574,6 +584,32 @@ public partial class CoreConfigV2rayService
|
||||
streamSettings.finalmask = hy2Finalmask;
|
||||
break;
|
||||
|
||||
case "masque":
|
||||
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var authValue = $"Basic {Utils.Base64Encode($"{_node.Username}:{_node.Password}")}";
|
||||
headers.Add("Authorization", authValue);
|
||||
if (!string.IsNullOrEmpty(protocolExtra?.MasqueHeaders))
|
||||
{
|
||||
var userHeaders = Utils.ParseHeaders(protocolExtra.MasqueHeaders)
|
||||
.GroupBy(x => x.Item1, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => string.Join(", ", g.Select(x => x.Item2)),
|
||||
StringComparer.OrdinalIgnoreCase
|
||||
);
|
||||
if (userHeaders.TryGetValue("Authorization", out var userAuthValue))
|
||||
{
|
||||
headers["Authorization"] = userAuthValue;
|
||||
}
|
||||
headers = userHeaders;
|
||||
}
|
||||
streamSettings.masqueSettings = new()
|
||||
{
|
||||
path = protocolExtra?.MasquePath.NullIfEmpty(),
|
||||
headers = headers.Count > 0 ? headers : null,
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
// raw
|
||||
if (headerType == Global.RawHeaderHttp)
|
||||
|
||||
@@ -8,7 +8,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
private readonly Config? _config = config;
|
||||
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
|
||||
private readonly Lock _runLock = new();
|
||||
private CancellationTokenSource? _runCts;
|
||||
private readonly List<CancellationTokenSource> _runCtsList = [];
|
||||
private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize;
|
||||
private readonly TimeSpan _delayInterval = TimeSpan.FromSeconds(config.SpeedTestItem.SpeedTestDelayInterval ?? 1);
|
||||
|
||||
@@ -18,11 +18,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
lock (_runLock)
|
||||
{
|
||||
_runCts?.Cancel();
|
||||
|
||||
runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
|
||||
_runCts = runCts;
|
||||
_runCtsList.Add(runCts);
|
||||
}
|
||||
|
||||
return RunLoopAsync(actionType, selecteds, runCts);
|
||||
@@ -30,17 +28,30 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
public void ExitLoop()
|
||||
{
|
||||
CancellationTokenSource? runCts;
|
||||
var counter = 0;
|
||||
List<CancellationTokenSource> listToCancel;
|
||||
|
||||
lock (_runLock)
|
||||
{
|
||||
runCts = _runCts;
|
||||
listToCancel = _runCtsList.ToList();
|
||||
counter = listToCancel.Count;
|
||||
}
|
||||
|
||||
if (runCts is not null)
|
||||
foreach (var cts in listToCancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
if (counter > 0)
|
||||
{
|
||||
_ = UpdateFunc("", ResUI.SpeedtestingStop);
|
||||
runCts.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +78,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
lock (_runLock)
|
||||
{
|
||||
if (ReferenceEquals(_runCts, runCts))
|
||||
{
|
||||
_runCts = null;
|
||||
}
|
||||
_runCtsList.Remove(runCts);
|
||||
}
|
||||
|
||||
runCts.Dispose();
|
||||
@@ -202,6 +210,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
var parallelOptions = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = lst.Count,
|
||||
CancellationToken = ct,
|
||||
};
|
||||
|
||||
@@ -283,6 +292,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
var parallelOptions = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = selecteds.Count,
|
||||
CancellationToken = ct,
|
||||
};
|
||||
|
||||
@@ -373,6 +383,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
|
||||
var parallelOptions = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = selecteds.Count,
|
||||
CancellationToken = ct,
|
||||
};
|
||||
|
||||
@@ -562,6 +573,10 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
await clientSocket.ConnectAsync(endPoint, linkedCts.Token).ConfigureAwait(false);
|
||||
responseTime = (int)timer.ElapsedMilliseconds;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
finally
|
||||
{
|
||||
timer.Stop();
|
||||
@@ -572,6 +587,12 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
||||
{
|
||||
List<List<ServerTestItem>> lstTest = [];
|
||||
if (lstSelected.Count == 0)
|
||||
{
|
||||
return lstTest;
|
||||
}
|
||||
|
||||
pageSize = Math.Max(pageSize, 1);
|
||||
var lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
|
||||
var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ namespace ServiceLib.Services.Statistics;
|
||||
public class StatisticsSingboxService
|
||||
{
|
||||
private readonly Config _config;
|
||||
private bool _exitFlag;
|
||||
private ClientWebSocket? webSocket;
|
||||
private CancellationTokenSource? _cts;
|
||||
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
|
||||
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
|
||||
private static readonly string _tag = "StatisticsSingboxService";
|
||||
@@ -15,40 +14,17 @@ public class StatisticsSingboxService
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = updateFunc;
|
||||
_exitFlag = false;
|
||||
|
||||
_ = Task.Factory.StartNew(
|
||||
Run,
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
|
||||
try
|
||||
{
|
||||
if (webSocket == null)
|
||||
{
|
||||
webSocket = new ClientWebSocket();
|
||||
await webSocket.ConnectAsync(new Uri(Url), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_exitFlag = true;
|
||||
if (webSocket != null)
|
||||
{
|
||||
webSocket.Abort();
|
||||
webSocket = null;
|
||||
}
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -58,53 +34,63 @@ public class StatisticsSingboxService
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
await Init();
|
||||
Close();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
while (!_exitFlag)
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||
{
|
||||
await Task.Delay(1000, token).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
if (webSocket != null)
|
||||
using var ws = new ClientWebSocket();
|
||||
await ws.ConnectAsync(new Uri(Url), token).ConfigureAwait(false);
|
||||
|
||||
var buffer = new byte[1024];
|
||||
while (ws.State == WebSocketState.Open
|
||||
&& !token.IsCancellationRequested)
|
||||
{
|
||||
if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed)
|
||||
var res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token);
|
||||
if (res.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
webSocket.Abort();
|
||||
webSocket = null;
|
||||
await Init();
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
using var ms = new MemoryStream();
|
||||
ms.Write(buffer, 0, res.Count);
|
||||
while (!res.EndOfMessage)
|
||||
{
|
||||
res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token).ConfigureAwait(false);
|
||||
ms.Write(buffer, 0, res.Count);
|
||||
}
|
||||
|
||||
if (webSocket.State != WebSocketState.Open)
|
||||
var result = Encoding.UTF8.GetString(ms.ToArray());
|
||||
if (!result.IsNotEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ParseOutput(result, out var up, out var down);
|
||||
|
||||
var buffer = new byte[1024];
|
||||
var res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
while (!res.CloseStatus.HasValue)
|
||||
if (_updateFunc != null)
|
||||
{
|
||||
var result = Encoding.UTF8.GetString(buffer, 0, res.Count);
|
||||
if (result.IsNotEmpty())
|
||||
await _updateFunc.Invoke(new ServerSpeedItem
|
||||
{
|
||||
ParseOutput(result, out var up, out var down);
|
||||
|
||||
await _updateFunc?.Invoke(new ServerSpeedItem()
|
||||
{
|
||||
ProxyUp = (long)(up / 1000),
|
||||
ProxyDown = (long)(down / 1000)
|
||||
});
|
||||
}
|
||||
res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
ProxyUp = (long)(up / 1000),
|
||||
ProxyDown = (long)(down / 1000),
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Delay(3000, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,33 +5,42 @@ public class StatisticsXrayService
|
||||
private const long linkBase = 1024;
|
||||
private ServerSpeedItem _serverSpeedItem = new();
|
||||
private readonly Config _config;
|
||||
private bool _exitFlag;
|
||||
private CancellationTokenSource? _cts;
|
||||
private readonly Func<ServerSpeedItem, Task>? _updateFunc;
|
||||
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars";
|
||||
private static readonly string _tag = "StatisticsXrayService";
|
||||
|
||||
public StatisticsXrayService(Config config, Func<ServerSpeedItem, Task> updateFunc)
|
||||
{
|
||||
_config = config;
|
||||
_updateFunc = updateFunc;
|
||||
_exitFlag = false;
|
||||
|
||||
_ = Task.Factory.StartNew(
|
||||
Run,
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_exitFlag = true;
|
||||
try
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog(_tag, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
while (!_exitFlag)
|
||||
Close();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
|
||||
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
|
||||
@@ -39,16 +48,20 @@ public class StatisticsXrayService
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(Url);
|
||||
var result = await HttpClientHelper.Instance.TryGetAsync(Url, token);
|
||||
if (result != null)
|
||||
{
|
||||
var server = ParseOutput(result) ?? new ServerSpeedItem();
|
||||
await _updateFunc?.Invoke(server);
|
||||
await _updateFunc!.Invoke(server);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
await Task.Delay(3000, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
|
||||
return await CheckUpdateAsync(downloadHandle, type, checkPreRelease, blProxy, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<string>> CheckHasUpdateOnlyAll(bool preRelease, bool blProxy = true, CancellationToken cancellationToken = default)
|
||||
public async Task<List<string>> CheckHasUpdateOnlyAll(bool blProxy = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var msgs = new List<string>();
|
||||
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
|
||||
@@ -121,6 +121,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
|
||||
continue;
|
||||
}
|
||||
|
||||
var preRelease = _config.CheckUpdateItem.CheckPreReleaseCoreTypes?.Contains(type.ToString()) ?? false;
|
||||
var result = await CheckHasUpdateOnly(type, preRelease, blProxy, cancellationToken);
|
||||
if (result.Success && result.Version != null)
|
||||
{
|
||||
|
||||
@@ -97,6 +97,12 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
[Reactive]
|
||||
public partial int GeckoMaxPacketSize { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public partial string MasquePath { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public partial string MasqueHeaders { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public partial string RawHeaderType { get; set; }
|
||||
|
||||
@@ -322,6 +328,8 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty;
|
||||
GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
MasquePath = protocolExtra.MasquePath ?? string.Empty;
|
||||
MasqueHeaders = protocolExtra.MasqueHeaders ?? string.Empty;
|
||||
|
||||
RawHeaderType = transport.RawHeaderType ?? Global.None;
|
||||
Host = transport.Host ?? string.Empty;
|
||||
@@ -443,6 +451,8 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
Hy2RealmUrl = realm?.ToUri().NullIfEmpty(),
|
||||
GeckoMinPacketSize = GeckoMinPacketSize > 0 ? GeckoMinPacketSize.ToString() : null,
|
||||
GeckoMaxPacketSize = GeckoMaxPacketSize > 0 ? GeckoMaxPacketSize.ToString() : null,
|
||||
MasquePath = MasquePath.NullIfEmpty(),
|
||||
MasqueHeaders = MasqueHeaders.NullIfEmpty(),
|
||||
});
|
||||
SelectedSource.SetTransportExtra(transport);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = [];
|
||||
public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; }
|
||||
[Reactive] public partial bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
|
||||
[Reactive] public partial bool EnableUpdateViaProxy { get; set; }
|
||||
|
||||
public CheckUpdateViewModel()
|
||||
@@ -33,20 +33,26 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
_ = UpdateView(_v2rayN, ex.Message);
|
||||
});
|
||||
|
||||
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
|
||||
EnableUpdateViaProxy = _config.CheckUpdateItem.UpdateViaProxy;
|
||||
|
||||
this.WhenAnyValue(x => x.EnableCheckPreReleaseUpdate)
|
||||
.SubscribeAsync(async _ => await OnCheckPreReleaseUpdateChanged());
|
||||
|
||||
this.WhenAnyValue(x => x.EnableUpdateViaProxy)
|
||||
.Subscribe(c => _ = OnUpdateViaProxyChanged());
|
||||
|
||||
RefreshCheckUpdateItems();
|
||||
AppEvents.HasUpdateNotified
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(bl => RefreshCheckUpdateItems(bl));
|
||||
|
||||
RefreshCheckUpdateItems(true);
|
||||
}
|
||||
|
||||
private void RefreshCheckUpdateItems()
|
||||
private void RefreshCheckUpdateItems(bool hasUpdate)
|
||||
{
|
||||
if (!hasUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var models = CoreInfoManager.Instance.GetCheckUpdateCoreTypes()
|
||||
.Select(t => GetCheckUpdateModel(t))
|
||||
.ToList();
|
||||
@@ -63,6 +69,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
return new()
|
||||
{
|
||||
IsSelected = false,
|
||||
IsCheckPreRelease = false,
|
||||
ShowCheckPreRelease = false,
|
||||
CoreType = coreType,
|
||||
IsGeoFile = false,
|
||||
Remarks = ResUI.menuCheckUpdate + $" ({ResUI.MsgNotSupport})",
|
||||
@@ -73,6 +81,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
return new()
|
||||
{
|
||||
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType.ToString()) ?? true,
|
||||
IsCheckPreRelease = _config.CheckUpdateItem.CheckPreReleaseCoreTypes?.Contains(coreType.ToString()) ?? false,
|
||||
ShowCheckPreRelease = CoreInfoManager.Instance.GetCheckPreRelease(coreType, true),
|
||||
CoreType = coreType,
|
||||
IsGeoFile = false,
|
||||
Remarks = lastResult ?? ResUI.menuCheckUpdate,
|
||||
@@ -84,22 +94,14 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
return new()
|
||||
{
|
||||
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(_geo) ?? true,
|
||||
IsCheckPreRelease = false,
|
||||
ShowCheckPreRelease = false,
|
||||
CoreType = null,
|
||||
IsGeoFile = true,
|
||||
Remarks = ResUI.menuCheckUpdate,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnCheckPreReleaseUpdateChanged()
|
||||
{
|
||||
if (_config.CheckUpdateItem.CheckPreReleaseUpdate == EnableCheckPreReleaseUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate;
|
||||
await SaveSelectedCoreTypes();
|
||||
}
|
||||
|
||||
private async Task OnUpdateViaProxyChanged()
|
||||
{
|
||||
if (_config.CheckUpdateItem.UpdateViaProxy == EnableUpdateViaProxy)
|
||||
@@ -117,6 +119,11 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
.Select(t => t.CoreTypeForStorage)
|
||||
.ToList();
|
||||
|
||||
_config.CheckUpdateItem.CheckPreReleaseCoreTypes =
|
||||
CheckUpdateModels.Where(t => t.IsCheckPreRelease == true)
|
||||
.Select(t => t.CoreTypeForStorage)
|
||||
.ToList();
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
}
|
||||
|
||||
@@ -157,7 +164,7 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask);
|
||||
var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, EnableCheckPreReleaseUpdate, EnableUpdateViaProxy);
|
||||
var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, item.IsCheckPreRelease, EnableUpdateViaProxy);
|
||||
if (result.Success && result.Version != null)
|
||||
{
|
||||
await UpdateView(item.CoreType, string.Format(ResUI.MsgCheckUpdateHasNewVersion, item.CoreType, result.Version));
|
||||
@@ -203,15 +210,11 @@ public partial class CheckUpdateViewModel : MyReactiveObject
|
||||
await UpdateView(_v2rayN, ResUI.MsgNotSupport);
|
||||
continue;
|
||||
}
|
||||
await CheckUpdateN(EnableCheckPreReleaseUpdate);
|
||||
}
|
||||
else if (item.CoreType is ECoreType.Xray or ECoreType.sing_box)
|
||||
{
|
||||
await CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
|
||||
await CheckUpdateN(item.IsCheckPreRelease);
|
||||
}
|
||||
else if (item.CoreType.HasValue)
|
||||
{
|
||||
await CheckUpdateCore(item, false);
|
||||
await CheckUpdateCore(item, item.IsCheckPreRelease);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,7 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||
cancelDisposable.DisposeWith(disposables);
|
||||
var token = cancelDisposable.Token;
|
||||
|
||||
Task.Factory.StartNew(
|
||||
async () => await GetClashConnectionsTask(token),
|
||||
token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
Task.Run(() => GetClashConnectionsTask(token));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,9 +124,9 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||
try
|
||||
{
|
||||
var numOfExecuted = 1;
|
||||
while (!token.IsCancellationRequested)
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
|
||||
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
await Task.Delay(1000, token);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
|
||||
@@ -61,12 +61,7 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
||||
cancelDisposable.DisposeWith(disposables);
|
||||
var token = cancelDisposable.Token;
|
||||
|
||||
Task.Factory.StartNew(
|
||||
async () => await GetClashProxiesTask(token),
|
||||
token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
Task.Run(() => GetClashProxiesTask(token));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,9 +108,9 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
||||
try
|
||||
{
|
||||
var numOfExecuted = 1;
|
||||
while (!token.IsCancellationRequested)
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
|
||||
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
await Task.Delay(1000, token);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
|
||||
@@ -32,6 +32,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddWireguardServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddAnytlsServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddNaiveServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddMasqueServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCustomServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddCustomOutboundServerCmd { get; }
|
||||
public ReactiveCommand<RxVoid, RxVoid> AddPolicyGroupServerCmd { get; }
|
||||
@@ -140,6 +141,10 @@ public partial class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await AddServerAsync(EConfigType.Naive);
|
||||
});
|
||||
AddMasqueServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(EConfigType.MASQUE);
|
||||
});
|
||||
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddServerAsync(EConfigType.Custom);
|
||||
@@ -522,7 +527,7 @@ public partial class MainWindowViewModel : MyReactiveObject
|
||||
public async Task AddServerViaImageAsync()
|
||||
{
|
||||
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
|
||||
await AddScanResultAsync(imageFileName);
|
||||
await ScanImageResult(imageFileName);
|
||||
}
|
||||
|
||||
public async Task ScanImageResult(string fileName)
|
||||
|
||||
@@ -113,6 +113,7 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
[Reactive] public partial string CoreType6 { get; set; }
|
||||
[Reactive] public partial string CoreType7 { get; set; }
|
||||
[Reactive] public partial string CoreType9 { get; set; }
|
||||
[Reactive] public partial string CoreType14 { get; set; }
|
||||
|
||||
#endregion CoreType
|
||||
|
||||
@@ -283,6 +284,10 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
case 9:
|
||||
CoreType9 = type;
|
||||
break;
|
||||
|
||||
case 14:
|
||||
CoreType14 = type;
|
||||
break;
|
||||
}
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
@@ -451,6 +456,10 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
type = CoreType9;
|
||||
break;
|
||||
|
||||
case 14:
|
||||
type = CoreType14;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -219,7 +219,8 @@ public partial class StatusBarViewModel : MyReactiveObject
|
||||
await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await RefreshRoutingsMenu();
|
||||
await InboundDisplayStatus();
|
||||
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true);
|
||||
// The view refreshes its icon on initialization; interaction handlers are not registered yet.
|
||||
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, false);
|
||||
|
||||
BlRouting = true;
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
@@ -783,6 +783,70 @@
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
|
||||
<Grid
|
||||
x:Name="gridMasque"
|
||||
Grid.Row="2"
|
||||
ColumnDefinitions="300,Auto"
|
||||
IsVisible="False"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbUsername}" />
|
||||
<TextBox
|
||||
x:Name="txtId14"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbId3}" />
|
||||
<TextBox
|
||||
x:Name="txtSecurity14"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbPath}" />
|
||||
<TextBox
|
||||
x:Name="txtPath14"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbHttpOutboundHeaders}" />
|
||||
<TextBox
|
||||
x:Name="txtHttpHeaders14"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
AcceptsReturn="True"
|
||||
Classes="TextArea"
|
||||
PlaceholderText="Header: Value"
|
||||
TextWrapping="NoWrap" />
|
||||
</Grid>
|
||||
|
||||
<Separator
|
||||
x:Name="sepa2"
|
||||
Grid.Row="3"
|
||||
|
||||
@@ -141,6 +141,13 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.MASQUE:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId14.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity14.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MasquePath, v => v.txtPath14.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MasqueHeaders, v => v.txtHttpHeaders14.Text).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.DisposeWith(disposables);
|
||||
@@ -194,7 +201,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void InitializeData(ProfileItem profileItem)
|
||||
@@ -284,6 +290,14 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
|
||||
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
|
||||
break;
|
||||
|
||||
case EConfigType.MASQUE:
|
||||
gridMasque.IsVisible = true;
|
||||
sepa2.IsVisible = false;
|
||||
gridTransport.IsVisible = false;
|
||||
cmbFingerprint.IsEnabled = false;
|
||||
cmbAlpn.IsEnabled = false;
|
||||
break;
|
||||
}
|
||||
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
|
||||
cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity;
|
||||
|
||||
@@ -24,16 +24,23 @@
|
||||
HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<Border
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
|
||||
<Grid
|
||||
Margin="{StaticResource Margin4}"
|
||||
ColumnDefinitions="300,200"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="300"
|
||||
SharedSizeGroup="BackupLabel" />
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="200"
|
||||
SharedSizeGroup="BackupButton" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
@@ -79,10 +86,17 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Theme="{DynamicResource CardBorder}">
|
||||
<Grid
|
||||
Margin="{StaticResource Margin4}"
|
||||
ColumnDefinitions="300,200"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="300"
|
||||
SharedSizeGroup="BackupLabel" />
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="200"
|
||||
SharedSizeGroup="BackupButton" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
|
||||
@@ -35,27 +35,17 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togEnableCheckPreReleaseUpdate"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<Button
|
||||
x:Name="btnCheckOnly"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Content="{x:Static resx:ResUI.menuCheckOnly}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnCheckUpdate"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Content="{x:Static resx:ResUI.menuCheckUpdate}" />
|
||||
Content="{x:Static resx:ResUI.menuCheckAndUpdate}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -75,8 +65,8 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Width="500" VerticalAlignment="Center">
|
||||
<Grid ColumnDefinitions="1*,1*,3*" RowDefinitions="Auto">
|
||||
<Panel Width="680" VerticalAlignment="Center">
|
||||
<Grid ColumnDefinitions="1*,3*,1*,3*" RowDefinitions="Auto">
|
||||
<ToggleSwitch
|
||||
x:Name="togAutoRefresh"
|
||||
Grid.Column="0"
|
||||
@@ -84,13 +74,32 @@
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsSelected}" />
|
||||
<TextBlock
|
||||
|
||||
<DockPanel
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource MarginLr4}"
|
||||
IsVisible="{Binding ShowCheckPreRelease}">
|
||||
<ToggleSwitch
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsCheckPreRelease}"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}"
|
||||
TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding CoreTypeForStorage}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Remarks}"
|
||||
|
||||
@@ -10,7 +10,6 @@ public partial class CheckUpdateView : ReactiveUserControl<CheckUpdateViewModel>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.CheckUpdateModels, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.EnableCheckPreReleaseUpdate, v => v.togEnableCheckPreReleaseUpdate.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableUpdateViaProxy, v => v.togEnableUpdateViaProxy.IsChecked).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CheckOnlyCmd, v => v.btnCheckOnly).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -23,17 +23,17 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnReset"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbReset}" />
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
ShowInTaskbar="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Window.Styles>
|
||||
<!-- Keep the log selected; hide only the strip when it has no other tabs. -->
|
||||
<Style Selector="TabControl.logPane /template/ ItemsPresenter#PART_ItemsPresenter">
|
||||
<Setter Property="IsVisible" Value="{Binding ShowClashUI}" />
|
||||
</Style>
|
||||
<Style Selector="TabControl.logPane /template/ Border#PART_BorderSeparator">
|
||||
<Setter Property="IsVisible" Value="{Binding ShowClashUI}" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
<dialogHost:DialogHost CloseOnClickAway="True" DisableOpeningAnimation="True">
|
||||
<DockPanel>
|
||||
<DockPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Top">
|
||||
@@ -45,6 +54,7 @@
|
||||
<MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
|
||||
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
|
||||
<MenuItem x:Name="menuAddWireguardServer" Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
|
||||
<MenuItem x:Name="menuAddMasqueServer" Header="{x:Static resx:ResUI.menuAddMasqueServer}" />
|
||||
<MenuItem x:Name="menuAddSocksServer" Header="{x:Static resx:ResUI.menuAddSocksServer}" />
|
||||
<MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" />
|
||||
<Separator />
|
||||
@@ -123,6 +133,7 @@
|
||||
<TabControl
|
||||
x:Name="tabMain"
|
||||
Grid.Column="2"
|
||||
Classes="logPane"
|
||||
HorizontalContentAlignment="Left">
|
||||
<TabItem x:Name="tabMsgView" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
|
||||
<TabItem x:Name="tabClashProxies" Header="{x:Static resx:ResUI.TbProxies}" />
|
||||
@@ -138,6 +149,7 @@
|
||||
<TabControl
|
||||
x:Name="tabMain1"
|
||||
Grid.Row="2"
|
||||
Classes="logPane"
|
||||
TabStripPlacement="Left">
|
||||
<TabItem x:Name="tabMsgView1" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
|
||||
<TabItem x:Name="tabClashProxies1" Header="{x:Static resx:ResUI.TbProxies}" />
|
||||
|
||||
@@ -46,6 +46,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
|
||||
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.AddMasqueServerCmd, v => v.menuAddMasqueServer).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);
|
||||
@@ -418,7 +419,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabMsgView.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
||||
@@ -429,7 +429,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||
this.OneWayBind(ViewModel, vm => vm.MsgViewModel, v => v.tabMsgView1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ClashConnectionsViewModel, v => v.tabClashConnections1.Content).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabMsgView1.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashProxies1.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections1.IsVisible).DisposeWith(currentLayoutDisposables);
|
||||
this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain1.SelectedIndex).DisposeWith(currentLayoutDisposables);
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
Width="200"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
VerticalContentAlignment="Center"
|
||||
PlaceholderText="{x:Static resx:ResUI.MsgFilterTitle}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.MsgFilterTitle}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.MsgFilterTitle}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnCopy"
|
||||
|
||||
@@ -47,9 +47,14 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
||||
if (togScrollToEnd.IsChecked ?? true)
|
||||
{
|
||||
txtMsg.ScrollToEnd();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
txtMsg.ScrollTo(txtMsg.LineCount, 0, AvaloniaEdit.Rendering.VisualYPosition.TextBottom, txtMsg.Bounds.Height, 0),
|
||||
DispatcherPriority.Background);
|
||||
// Let layout finish without a nested dispatcher loop during a native window drag.
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (togScrollToEnd.IsChecked ?? true)
|
||||
{
|
||||
txtMsg.ScrollTo(txtMsg.LineCount, 0, AvaloniaEdit.Rendering.VisualYPosition.TextBottom, txtMsg.Bounds.Height, 0);
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
@@ -1192,7 +1192,7 @@
|
||||
<Grid
|
||||
Margin="{StaticResource Margin4}"
|
||||
ColumnDefinitions="Auto,Auto"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
@@ -1296,6 +1296,19 @@
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="9"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="MASQUE" />
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType14"
|
||||
Grid.Row="9"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
@@ -46,6 +46,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||
cmbCoreType6.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType7.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType9.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType14.ItemsSource = Global.CoreTypes;
|
||||
|
||||
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(Global.SpeedTestConcurrencyCountMin, 20).ToList();
|
||||
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList();
|
||||
@@ -145,6 +146,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
|
||||
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType14, v => v.cmbCoreType14.SelectedValue).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
@@ -72,7 +72,8 @@
|
||||
Margin="{StaticResource MarginLr4}"
|
||||
VerticalContentAlignment="Center"
|
||||
Text="{Binding ServerFilter, Mode=TwoWay}"
|
||||
PlaceholderText="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
</WrapPanel>
|
||||
|
||||
<!-- Profiles grid -->
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
Width="200"
|
||||
Margin="{StaticResource MarginLr4}"
|
||||
VerticalContentAlignment="Center"
|
||||
PlaceholderText="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnAutofitColumnWidth"
|
||||
|
||||
@@ -439,6 +439,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
||||
|
||||
private static readonly DataFormat<ProfileItemModel> LstProfilesRowFormat =
|
||||
DataFormat.CreateInProcessFormat<ProfileItemModel>("LstProfilesRow");
|
||||
|
||||
private (Point, PointerPressedEventArgs)? _dragStartPoint;
|
||||
|
||||
private void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
|
||||
@@ -208,12 +208,12 @@
|
||||
</StackPanel>
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
@@ -136,7 +136,8 @@
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}" />
|
||||
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}"
|
||||
TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock
|
||||
@@ -230,7 +231,8 @@
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||
<Button
|
||||
x:Name="btnSelectPrevProfile"
|
||||
Grid.Row="10"
|
||||
@@ -250,7 +252,8 @@
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||
PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||
<Button
|
||||
x:Name="btnSelectNextProfile"
|
||||
Grid.Row="11"
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="True" />
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
<CETCompat>false</CETCompat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64' Or '$(RuntimeIdentifier)' == 'osx-arm64'">
|
||||
<RuntimeHostConfigurationOption Include="System.Net.Security.UseNetworkFramework" Value="true" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.AvaloniaEdit" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid">
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
@@ -1000,6 +1000,89 @@
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="gridMasque"
|
||||
Grid.Row="2"
|
||||
Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbUsername}" />
|
||||
<TextBox
|
||||
x:Name="txtId14"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbId3}" />
|
||||
<TextBox
|
||||
x:Name="txtSecurity14"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbPath}" />
|
||||
<TextBox
|
||||
x:Name="txtPath14"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbHttpOutboundHeaders}" />
|
||||
<TextBox
|
||||
x:Name="txtHeaders14"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
materialDesign:HintAssist.Hint="Header: Value"
|
||||
AcceptsReturn="True"
|
||||
MaxLines="12"
|
||||
MinLines="3"
|
||||
Style="{StaticResource MyOutlinedTextBox}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
|
||||
<Separator
|
||||
x:Name="sepa2"
|
||||
Grid.Row="3"
|
||||
|
||||
@@ -139,6 +139,13 @@ public partial class AddServerWindow
|
||||
this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.MASQUE:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId14.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity14.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MasquePath, v => v.txtPath14.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MasqueHeaders, v => v.txtHeaders14.Text).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.DisposeWith(disposables);
|
||||
@@ -284,6 +291,14 @@ public partial class AddServerWindow
|
||||
|
||||
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
|
||||
break;
|
||||
|
||||
case EConfigType.MASQUE:
|
||||
gridMasque.Visibility = Visibility.Visible;
|
||||
sepa2.Visibility = Visibility.Collapsed;
|
||||
gridTransport.Visibility = Visibility.Collapsed;
|
||||
cmbFingerprint.IsEnabled = false;
|
||||
cmbAlpn.IsEnabled = false;
|
||||
break;
|
||||
}
|
||||
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
|
||||
cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
Style="{StaticResource ToolbarTextBlock}" />
|
||||
</DockPanel>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<materialDesign:Card Width="Auto" Margin="{StaticResource Margin8}">
|
||||
<Grid Margin="{StaticResource Margin8}">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -40,8 +40,14 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="300"
|
||||
SharedSizeGroup="BackupLabel" />
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="200"
|
||||
SharedSizeGroup="BackupButton" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
@@ -98,8 +104,14 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="300"
|
||||
SharedSizeGroup="BackupLabel" />
|
||||
<ColumnDefinition
|
||||
Width="Auto"
|
||||
MinWidth="200"
|
||||
SharedSizeGroup="BackupButton" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
x:TypeArguments="vms:CheckUpdateViewModel"
|
||||
Style="{StaticResource ViewGlobal}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
|
||||
</UserControl.Resources>
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
<StackPanel
|
||||
Margin="{StaticResource Margin8}"
|
||||
@@ -32,28 +34,18 @@
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
|
||||
<ToggleButton
|
||||
x:Name="togEnableCheckPreReleaseUpdate"
|
||||
Margin="{StaticResource Margin8}"
|
||||
HorizontalAlignment="Left" />
|
||||
|
||||
<Button
|
||||
x:Name="btnCheckOnly"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Content="{x:Static resx:ResUI.menuCheckOnly}"
|
||||
Style="{StaticResource DefButton}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnCheckUpdate"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Content="{x:Static resx:ResUI.menuCheckUpdate}"
|
||||
Content="{x:Static resx:ResUI.menuCheckAndUpdate}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
</StackPanel>
|
||||
@@ -71,7 +63,7 @@
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Width="500"
|
||||
Width="660"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Padding="0"
|
||||
VerticalAlignment="Center">
|
||||
@@ -81,6 +73,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="3*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
@@ -91,12 +84,32 @@
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsSelected}" />
|
||||
<TextBlock
|
||||
|
||||
<DockPanel
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource MarginLeftRight4}"
|
||||
Visibility="{Binding ShowCheckPreRelease, Converter={StaticResource BoolToVisConverter}}">
|
||||
<ToggleButton
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsCheckPreRelease}" />
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}"
|
||||
TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ListItemTitle}"
|
||||
Text="{Binding CoreTypeForStorage}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Grid.Column="3"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ListItemSubTitle}"
|
||||
Text="{Binding Remarks}"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
|
||||
@@ -10,7 +10,6 @@ public partial class CheckUpdateView
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.CheckUpdateModels, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.EnableCheckPreReleaseUpdate, v => v.togEnableCheckPreReleaseUpdate.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.EnableUpdateViaProxy, v => v.togEnableUpdateViaProxy.IsChecked).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CheckOnlyCmd, v => v.btnCheckOnly).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -25,19 +25,19 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnReset"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbReset}"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -112,6 +112,10 @@
|
||||
x:Name="menuAddWireguardServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
|
||||
<MenuItem
|
||||
x:Name="menuAddMasqueServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
Header="{x:Static resx:ResUI.menuAddMasqueServer}" />
|
||||
<MenuItem
|
||||
x:Name="menuAddSocksServer"
|
||||
Height="{StaticResource MenuItemHeight}"
|
||||
|
||||
@@ -46,6 +46,7 @@ public partial class MainWindow
|
||||
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
|
||||
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.AddMasqueServerCmd, v => v.menuAddMasqueServer).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);
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.MsgFilterTitle}"
|
||||
IsEditable="True"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
Style="{StaticResource DefComboBox}"
|
||||
ToolTip="{x:Static resx:ResUI.MsgFilterTitle}" />
|
||||
<Button
|
||||
x:Name="btnCopy"
|
||||
Width="24"
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
@@ -1473,6 +1473,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -1597,6 +1598,21 @@
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="9"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin8}"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="MASQUE" />
|
||||
<ComboBox
|
||||
x:Name="cmbCoreType14"
|
||||
Grid.Row="9"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin8}"
|
||||
Style="{StaticResource DefComboBox}" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
@@ -42,6 +42,7 @@ public partial class OptionSettingWindow
|
||||
cmbCoreType6.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType7.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType9.ItemsSource = Global.CoreTypes;
|
||||
cmbCoreType14.ItemsSource = Global.CoreTypes;
|
||||
|
||||
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(Global.SpeedTestConcurrencyCountMin, 20).ToList();
|
||||
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).ToList();
|
||||
@@ -141,6 +142,7 @@ public partial class OptionSettingWindow
|
||||
this.Bind(ViewModel, vm => vm.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType9, v => v.cmbCoreType9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType14, v => v.cmbCoreType14.Text).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
@@ -77,7 +77,8 @@
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
Style="{StaticResource DefTextBox}"
|
||||
ToolTip="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
</WrapPanel>
|
||||
<DataGrid
|
||||
x:Name="lstProfiles"
|
||||
|
||||
@@ -149,5 +149,6 @@ public partial class ProfilesSelectWindow
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Event
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
|
||||
Style="{StaticResource DefTextBox}" />
|
||||
Style="{StaticResource DefTextBox}"
|
||||
ToolTip="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnAutofitColumnWidth"
|
||||
|
||||
@@ -237,13 +237,13 @@
|
||||
</StackPanel>
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -74,13 +74,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnSave"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Content="{x:Static resx:ResUI.TbConfirm}"
|
||||
IsDefault="True"
|
||||
Style="{StaticResource DefButton}" />
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource MarginLeftRight8}"
|
||||
Content="{x:Static resx:ResUI.TbCancel}"
|
||||
IsCancel="true"
|
||||
@@ -174,7 +174,8 @@
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource ToolbarTextBlock}"
|
||||
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}" />
|
||||
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}"
|
||||
TextWrapping="Wrap" />
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock
|
||||
@@ -282,7 +283,8 @@
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||
AcceptsReturn="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
Style="{StaticResource MyOutlinedTextBox}"
|
||||
ToolTip="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||
<Button
|
||||
x:Name="btnSelectPrevProfile"
|
||||
Grid.Row="10"
|
||||
@@ -307,7 +309,8 @@
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
|
||||
AcceptsReturn="True"
|
||||
Style="{StaticResource MyOutlinedTextBox}" />
|
||||
Style="{StaticResource MyOutlinedTextBox}"
|
||||
ToolTip="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||
<Button
|
||||
x:Name="btnSelectNextProfile"
|
||||
Grid.Row="11"
|
||||
|
||||
Reference in New Issue
Block a user