Compare commits

...

36 Commits

Author SHA1 Message Date
2dust 1883178b5f up 7.25.6 2026-10-11 13:55:36 +08:00
2dust 1cde521d75 Prevent zip path traversal in app upgrade
Hardened upgrade extraction by validating each zip entry resolves under the app startup directory before writing files, and aborting with a clear failure message on suspicious paths. Added shared path utilities for directory containment checks and OS-aware path string comparison, then reused them for executable matching and `bin` prefix checks during extraction.

https://github.com/2dust/v2rayN/issues/10313
2026-10-11 13:52:28 +08:00
Ali Ahmadi 2407d44f4f Fix empty latency tests and Linux startup errors (#10335)
* Fix overflow when speed tests have no eligible profiles

* Preserve core executable permissions in Debian packages

* Avoid requesting an icon refresh before view initialization
2026-10-11 11:06:46 +08:00
dependabot[bot] fbf822d74c Bump TUnit from 1.72.16 to 1.73.19 (#10334)
---
updated-dependencies:
- dependency-name: TUnit
  dependency-version: 1.73.19
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-11 10:56:36 +08:00
liu-xindi 4869a6cbba Route IPv6 into the Xray TUN when EnableIPv6Address is on (#10329)
* Route IPv6 into the Xray TUN on NAT66 hosts and when IPv6 is enabled

#10080 dropped ::/0 from autoSystemRoutingTable whenever the host held no
address inside 2000::/3. A host behind NAT66 holds only unique local
addresses, yet it reaches the IPv6 internet through its IPv6 default
gateway. With ::/0 gone, its IPv6 follows the system default route,
bypasses the tunnel and exposes the real IPv6 address (#10327).

The check also overrode EnableIPv6Address. Before #9930, enabling it
always added ::/0; since #10080 the option only assigned the interface
address, so users who had enabled it were affected as well.

Route ::/0 when EnableIPv6Address is on, and otherwise when the host has
IPv6 connectivity: a global IPv6 address or an IPv6 default gateway.
Routes whose next hop is ::, such as the one the tunnel installs, do not
count. A host with neither, as in #10051, still skips ::/0. The
RouteExcludeAddress branch uses the same decision.

Reproduced in network namespaces with Xray 26.7.28: on a NAT66 client the
7.25.4 TUN inbound let curl -6 reach the echo server from the NAT66
address, while the inbound generated by this change sends it through the
proxy, with EnableIPv6Address on or off.

Fixes #10327

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Simplify: route ::/0 when EnableIPv6Address is on

Drop the rename and the gateway detection; keep HasGlobalIPv6Address
and let EnableIPv6Address override it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142Bna7M2GhesX7P7d4CSrt

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:55:39 +08:00
DHR60 b501efbc45 Fix (#10326) 2026-10-10 09:14:30 +08:00
Miheichev Aleksandr Sergeevich 5ea8ae6420 fix(ui): wrap the pre-release label in the update dialog (#10303)
The per-core pre-release switch and its label sit in a horizontal
StackPanel inside a fixed-width column of the update dialog.
Translations longer than the column do not fit: in Avalonia the label
runs over the core name and the update status (Russian, French,
Hungarian, Persian), and in WPF it is cut off. Use a DockPanel so that
the label wraps inside its column, as the interval label of the
subscription window does since #10292. The dialog keeps its width.

In Avalonia the panel also had a vertical margin on top of the
margin of the switch, so the two rows with the switch were 8 px taller
than the other rows. Use a horizontal-only margin, as the WPF view
already does.
2026-10-07 11:11:27 +08:00
Miheichev Aleksandr Sergeevich fa9d4a4769 fix(ui): line up the backup dialog columns again (#10302)
#10292 made the label and button columns of the backup view Auto with
a minimum width, so that longer labels and buttons fit. Each of the two
cards sizes its own grid, so when the remote (WebDAV) buttons are wider
than the local ones, the local buttons no longer sit under the remote
ones and the separators of the two cards differ in length. This shows
in English and in every other language except Chinese.

Share the column widths of the two cards with SharedSizeGroup: both
cards line up again, and the long labels and buttons still fit. In
Avalonia the buttons stay centred in the shared column; in WPF they
fill it as before, so the local buttons get as wide as the remote ones.
2026-10-07 11:09:32 +08:00
DHR60 771ef3192a Update cert (#10308) 2026-10-07 11:04:58 +08:00
2dust f5747bb321 up 7.25.5 2026-10-05 16:43:51 +08:00
ariansharifi d099e44ff6 Fix subscription custom configs ignoring custom config socks port (#10296)
* Fix subscription custom configs ignoring custom config socks port

Full Xray/sing-box configs imported from a subscription no longer got the
subscription's "Custom config socks port" (SubItem.PreSocksPort) after
eff5845 and 9638b4c. 7.24.4 and earlier copied it onto every imported
config, and the Clash, Hysteria2 and raw-file paths still do.

Without it, GetPreSocksItem builds no pre-socks core for these profiles,
so they cannot be used in TUN mode. Setting the port on the profile is
not a workaround, because every subscription update re-creates the
profiles.

* Add regression test for subscription custom config socks port
2026-10-05 16:36:52 +08:00
DHR60 c63d271d16 Up TUnit.Assertions.Should (#10294) 2026-10-05 16:27:38 +08:00
Miheichev Aleksandr Sergeevich 6152cab494 fix(ui): make cut-off button labels and input hints readable (#10292)
* fix(ui): let update and backup buttons fit their labels

The update dialog gives its two buttons a fixed 100 px width, and the
backup view puts its buttons in a fixed 200 px grid column, so longer
labels are cut off, for example "Only Chec", "Check and" and "Backup
to remote (WebD" in English. Use MinWidth="100" for the update buttons
and an Auto column with MinWidth="200" for the backup buttons: short
labels keep their current size and long ones fit.

The "Automatic update interval (minutes)" label of the subscription
window is cut off the same way between the switch and the interval
field, so let it wrap.

* fix(ui): show cut-off input hints as tooltips

The hints of the previous and next proxy remarks fields in the
subscription window and of the server and log filters are wider than
their fields, so their end is cut off and there is no way to read it.
Show the same text as a tooltip, as the custom config socks port field
of the subscription window already does.

* fix(ui): let dialog buttons and backup labels fit longer translations

The Confirm and Cancel buttons of the editing and settings windows,
and the Reset button of the hotkey window, have a fixed 100 px width,
and the backup view puts its labels in a fixed 300 px column.
Translations longer than the English text are cut off there: the
Russian "Подтвердить" reads "Подтверди", and "Резервное копирование
на удалённый сервер (WebDAV)" loses its end. Use MinWidth="100" for
these buttons, as the message box already does with MinWidth="90",
and an Auto label column with MinWidth="300".
2026-10-05 16:27:19 +08:00
2dust 3187eeef79 Code clean 2026-10-04 11:41:34 +08:00
Miheichev Aleksandr Sergeevich 3364ec0e57 i18n(ru): translate the update dialog buttons in full (#10291)
Add the Russian string for menuCheckAndUpdate, which #10282 added to
ResUI.resx, as "Проверить и обновить". Restore the full Russian text
of menuCheckOnly ("Только проверить") and menuCheckUpdate ("Проверить
обновления"), which were shortened only to fit the fixed 100 px
buttons of the update dialog; they fit once the buttons are sized by
their labels.
2026-10-04 11:08:10 +08:00
dependabot[bot] 23033fa002 Bump TUnit from 1.68.17 to 1.72.16 (#10288)
---
updated-dependencies:
- dependency-name: TUnit
  dependency-version: 1.72.16
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-04 11:07:42 +08:00
Eliot the Cougar 125cbb18a1 Keep Xray startup logs visible in horizontal and vertical layouts (#10272)
* Keep Xray log content visible in separate log layouts

* Revert wpf

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-10-04 11:06:06 +08:00
2dust f85326cf0e Add per-core prerelease update selection
Replaces the global prerelease toggle with per-core prerelease options in both WPF and Avalonia update views, and persists selections via `CheckPreReleaseCoreTypes` in config. Update checks now resolve prerelease mode per selected core (including auto-check flow), and the sing-box max-version lock is removed to allow newer versions.
2026-10-03 14:58:45 +08:00
Tosd 3666fc549d Resx: clarify update button wording (#10282) 2026-10-02 15:08:27 +08:00
Miheichev Aleksandr Sergeevich a147249f95 i18n(ru): translate the Add [MASQUE] menu item (#10276)
Add the Russian string for menuAddMasqueServer, which #10233 added to
ResUI.resx. Without it the Russian UI shows this one item of the
Configuration menu in English. The text follows the other
Add [protocol] items: "Добавить сервер [MASQUE]".
2026-10-02 15:06:49 +08:00
Eliot the Cougar a28f68c3a7 Avoid reentrant log scrolling during Avalonia window dragging (#10271) 2026-10-01 19:55:30 +08:00
Ezra-Y 1a689f869c Fix macOS TUN interface name collisions (#10270) 2026-10-01 19:51:08 +08:00
DHR60 50023a5976 Xray tun features (#10264)
* Xray tun features

* Try sync
2026-10-01 14:55:09 +08:00
DHR60 c659ca8af8 Add MASQUE support (#10233)
* Add MASQUE support

* Add SettingsCoreType
2026-10-01 14:49:45 +08:00
2dust 7d6a967c18 up 7.25.4 2026-09-30 14:38:34 +08:00
2dust 6c2edcb382 Revert "Bump Avalonia.Desktop from 12.1.2 to 12.1.3 (#10216)"
This reverts commit e1cb99cd6e.
2026-09-30 14:34:07 +08:00
2dust 086c991464 up 7.25.3 2026-09-30 09:55:08 +08:00
Alice Apate a9da04acc2 Add support for TLS1.3-only subscription URL for macOS (#10256)
* add support for TLS1.3-only subscription url for macOS

* Update v2rayN.Desktop.csproj

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-09-30 09:54:26 +08:00
2dust e0c790eeb2 Tune speedtest parallelism
Set `MaxDegreeOfParallelism` explicitly to the current selection size in three speed test parallel sections so task fan-out matches the workload. Also wrapped socket connect timing in a catch block that ignores connection exceptions, preventing a failed connect from aborting the flow while still completing cleanup.
https://github.com/2dust/v2rayN/issues/10246
2026-09-28 10:28:32 +08:00
2dust d433aa3b04 Fix
https://github.com/2dust/v2rayN/issues/10236
2026-09-26 11:43:22 +08:00
dependabot[bot] 91a7ed65d5 Bump ReactiveUI from 24.2.0 to 24.3.0 (#10231)
---
updated-dependencies:
- dependency-name: ReactiveUI
  dependency-version: 24.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-26 11:12:07 +08:00
Miheichev Aleksandr Sergeevich 984e685ab3 chore(deps): update Downloader to 5.9.8 (#10222)
Downloader 5.9.6 -> 5.9.8

5.9.7 adds RemoteFileInfo.ContentType. 5.9.8 fixes stop/dispose races:
a cancelled multi-chunk download could report a NullReferenceException
as its error, and a Dispose racing the completion signal could swallow
DownloadFileCompleted. Nothing DownloaderHelper calls changed its API.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 11:11:20 +08:00
DHR60 29f99ceacc Fix speed test (#10219) 2026-09-26 11:10:30 +08:00
DHR60 93d8174dbe Fix async (#10218)
* Fix async

* Fix async for statistics

* Try fix pac async

* Fix
2026-09-26 11:08:36 +08:00
DHR60 fa1e201c76 Fix sing-box dns (#10234) 2026-09-26 10:39:29 +08:00
dependabot[bot] e1cb99cd6e Bump Avalonia.Desktop from 12.1.2 to 12.1.3 (#10216)
---
updated-dependencies:
- dependency-name: Avalonia.Desktop
  dependency-version: 12.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-23 11:03:05 +08:00
96 changed files with 1092 additions and 482 deletions
+9
View File
@@ -676,6 +676,15 @@ EOF
find "$stage/opt/v2rayN" -type f -exec chmod 0644 {} + find "$stage/opt/v2rayN" -type f -exec chmod 0644 {} +
[[ -f "$stage/opt/v2rayN/v2rayN" ]] && chmod 0755 "$stage/opt/v2rayN/v2rayN" || true [[ -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" deb_out="$OUTPUT_DIR/v2rayn_${VERSION}_${deb_arch}.deb"
dpkg-deb --root-owner-group --build "$stage" "$deb_out" dpkg-deb --root-owner-group --build "$stage" "$deb_out"
+10 -3
View File
@@ -45,6 +45,8 @@ internal class UpgradeApp
var thisAppOldFile = $"{Utils.GetExePath()}.tmp"; var thisAppOldFile = $"{Utils.GetExePath()}.tmp";
File.Delete(thisAppOldFile); File.Delete(thisAppOldFile);
var splitKey = "/"; var splitKey = "/";
var allowedBaseDir = Utils.StartupPath();
var pathComparison = Utils.GetPathComparison();
using var archive = ZipFile.OpenRead(fileName); using var archive = ZipFile.OpenRead(fileName);
foreach (var entry in archive.Entries) foreach (var entry in archive.Entries)
@@ -65,16 +67,21 @@ internal class UpgradeApp
} }
var fullName = string.Join(splitKey, lst[1..lst.Length]); 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); File.Move(Utils.GetExePath(), thisAppOldFile);
} }
var entryOutputPath = Utils.GetPath(fullName);
Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!);
//In the bin folder, if the file already exists, it will be skipped //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; continue;
} }
+22
View File
@@ -24,6 +24,28 @@ internal class Utils
return Path.Combine(startupPath, fileName); 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 string V2rayN => "v2rayN";
public static void StartV2RayN() public static void StartV2RayN()
+1 -1
View File
@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.25.2</Version> <Version>7.25.6</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
+4 -4
View File
@@ -13,11 +13,11 @@
<PackageVersion Include="IPNetwork2" Version="4.3.0" /> <PackageVersion Include="IPNetwork2" Version="4.3.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.2" /> <PackageVersion Include="ReactiveUI.Avalonia" Version="12.1.2" />
<PackageVersion Include="CliWrap" Version="3.10.5" /> <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="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" /> <PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
<PackageVersion Include="QRCoder" Version="1.8.0" /> <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.SourceGenerators" Version="3.2.0" />
<PackageVersion Include="ReactiveUI.WPF" Version="24.2.0" /> <PackageVersion Include="ReactiveUI.WPF" Version="24.2.0" />
<PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" /> <PackageVersion Include="Semi.Avalonia" Version="12.1.0.1" />
@@ -27,8 +27,8 @@
<PackageVersion Include="sqlite-net-e" Version="1.11.285" /> <PackageVersion Include="sqlite-net-e" Version="1.11.285" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" /> <PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.4.1" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" /> <PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="TUnit" Version="1.68.17" /> <PackageVersion Include="TUnit" Version="1.73.19" />
<PackageVersion Include="TUnit.Assertions.Should" Version="1.65.38-beta" /> <PackageVersion Include="TUnit.Assertions.Should" Version="1.72.16-beta" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <PackageVersion Include="WebDav.Client" Version="2.9.0" />
<PackageVersion Include="YamlDotNet" Version="18.1.0" /> <PackageVersion Include="YamlDotNet" Version="18.1.0" />
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" /> <PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
@@ -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; namespace ServiceLib.Tests.CoreConfig;
internal static class CoreConfigTestFactory internal static class CoreConfigTestFactory
@@ -716,7 +716,6 @@ public class CoreConfigSingboxServiceTests
} }
} }
[Test] [Test]
public async Task GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson() public async Task GenerateClientConfigContent_CustomOutbound_ShouldReplaceWithUserCustomOutboundJson()
{ {
@@ -587,13 +587,11 @@ public class CoreConfigV2rayServiceTests
} }
[Test] [Test]
[Arguments(true)] public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6()
[Arguments(false)]
public async Task GenerateClientConfigContent_Tun_ShouldSkipIPv6RouteWithoutGlobalIPv6(bool enableIPv6Address)
{ {
// A host without a global IPv6 address has no IPv6 traffic that could bypass the tunnel, // 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. // 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); CoreConfigTestFactory.BindAppManagerConfig(config);
var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main"); var node = CoreConfigTestFactory.CreateVmessNode(ECoreType.Xray, "n-main", "main");
@@ -611,6 +609,26 @@ public class CoreConfigV2rayServiceTests
await ipv6Routes.Should().BeEmpty(); 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] [Test]
public async Task GenerateClientConfigContent_TunRouteExcludeAddress_ShouldSkipIPv6RangesWithoutGlobalIPv6() 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])!;
}
}
+30 -1
View File
@@ -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 #endregion Conversion Functions
#region Data Checks #region Data Checks
@@ -1416,7 +1441,11 @@ public class Utils
if (File.Exists(fileName)) if (File.Exists(fileName))
{ {
var currentMode = File.GetUnixFileMode(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; return true;
} }
} }
+1
View File
@@ -15,6 +15,7 @@ public enum EConfigType
Anytls = 11, Anytls = 11,
Naive = 12, Naive = 12,
Outbound = 13, Outbound = 13,
MASQUE = 14,
PolicyGroup = 101, PolicyGroup = 101,
ProxyChain = 102, ProxyChain = 102,
} }
+4 -1
View File
@@ -261,7 +261,8 @@ public class Global
{ EConfigType.TUIC, "tuic" }, { EConfigType.TUIC, "tuic" },
{ EConfigType.WireGuard, "wireguard" }, { EConfigType.WireGuard, "wireguard" },
{ EConfigType.Anytls, "anytls" }, { EConfigType.Anytls, "anytls" },
{ EConfigType.Naive, "naive" } { EConfigType.Naive, "naive" },
{ EConfigType.MASQUE, "masque" },
}; };
public static readonly List<string> VmessSecurities = public static readonly List<string> VmessSecurities =
@@ -371,6 +372,7 @@ public class Global
EConfigType.Trojan, EConfigType.Trojan,
EConfigType.Hysteria2, EConfigType.Hysteria2,
EConfigType.WireGuard, EConfigType.WireGuard,
EConfigType.MASQUE,
EConfigType.SOCKS, EConfigType.SOCKS,
EConfigType.HTTP, EConfigType.HTTP,
]; ];
@@ -386,6 +388,7 @@ public class Global
EConfigType.Anytls, EConfigType.Anytls,
EConfigType.Naive, EConfigType.Naive,
EConfigType.WireGuard, EConfigType.WireGuard,
EConfigType.MASQUE,
EConfigType.SOCKS, EConfigType.SOCKS,
EConfigType.HTTP, EConfigType.HTTP,
]; ];
@@ -49,6 +49,7 @@ public class CoreConfigContextBuilder
RoutingItem = await ConfigHandler.GetDefaultRouting(config), RoutingItem = await ConfigHandler.GetDefaultRouting(config),
IsWindows = Utils.IsWindows(), IsWindows = Utils.IsWindows(),
IsMacOS = Utils.IsMacOS(), IsMacOS = Utils.IsMacOS(),
IsLinux = Utils.IsLinux(),
HasGlobalIPv6Address = Utils.HasGlobalIPv6Address(), HasGlobalIPv6Address = Utils.HasGlobalIPv6Address(),
ProtectCoreTypeList = config.TunModeItem.EnableTun ? [ECoreType.Xray, ECoreType.sing_box] : [] ProtectCoreTypeList = config.TunModeItem.EnableTun ? [ECoreType.Xray, ECoreType.sing_box] : []
}; };
+30 -3
View File
@@ -292,6 +292,7 @@ public static class ConfigHandler
EConfigType.WireGuard => await AddWireguardServer(config, item), EConfigType.WireGuard => await AddWireguardServer(config, item),
EConfigType.Anytls => await AddAnytlsServer(config, item), EConfigType.Anytls => await AddAnytlsServer(config, item),
EConfigType.Naive => await AddNaiveServer(config, item), EConfigType.Naive => await AddNaiveServer(config, item),
EConfigType.MASQUE => await AddMasqueServer(config, item),
_ => -1, _ => -1,
}; };
return ret; return ret;
@@ -992,6 +993,29 @@ public static class ConfigHandler
return 0; 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> /// <summary>
/// Sort the server list by the specified column /// Sort the server list by the specified column
/// Updates the sort order in the profile extension data /// 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.WireGuard => await AddWireguardServer(config, profileItem, false),
EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false), EConfigType.Anytls => await AddAnytlsServer(config, profileItem, false),
EConfigType.Naive => await AddNaiveServer(config, profileItem, false), EConfigType.Naive => await AddNaiveServer(config, profileItem, false),
EConfigType.MASQUE => await AddMasqueServer(config, profileItem, false),
_ => -1, _ => -1,
}; };
@@ -1753,7 +1778,7 @@ public static class ConfigHandler
} }
if (lstProfiles.Count > 0) if (lstProfiles.Count > 0)
{ {
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub); var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub, subItem);
if (count > 0) if (count > 0)
{ {
return count; return count;
@@ -1799,7 +1824,7 @@ public static class ConfigHandler
if (lstProfiles?.Count > 0) if (lstProfiles?.Count > 0)
{ {
var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub); var count = await AddBatchCustomServers(config, lstProfiles, subid, isSub, subItem);
if (count > 0) if (count > 0)
{ {
return count; return count;
@@ -1813,7 +1838,8 @@ public static class ConfigHandler
Config config, Config config,
List<ProfileItem> lstProfiles, List<ProfileItem> lstProfiles,
string subid, string subid,
bool isSub) bool isSub,
SubItem? subItem)
{ {
var count = 0; var count = 0;
foreach (var it in lstProfiles) foreach (var it in lstProfiles)
@@ -1823,6 +1849,7 @@ public static class ConfigHandler
if (it.ConfigType == EConfigType.Custom) if (it.ConfigType == EConfigType.Custom)
{ {
it.PreSocksPort = subItem?.PreSocksPort;
if (await AddCustomServer(config, it, true) == 0) if (await AddCustomServer(config, it, true) == 0)
{ {
count++; count++;
@@ -3,6 +3,7 @@ namespace ServiceLib.Handler.SysProxy;
public static class SysProxyHandler public static class SysProxyHandler
{ {
private static readonly string _tag = "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) public static async Task<bool> UpdateSysProxy(Config config, bool forceDisable)
{ {
@@ -56,7 +57,7 @@ public static class SysProxyHandler
if (type != ESysProxyType.Pac && Utils.IsWindows()) if (type != ESysProxyType.Pac && Utils.IsWindows())
{ {
PacManager.Instance.Stop(); _pacManager.Value.Stop();
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -110,7 +111,7 @@ public static class SysProxyHandler
private static async Task SetWindowsProxyPac(int port) private static async Task SetWindowsProxyPac(int port)
{ {
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac); 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}"; var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4); ProxySettingWindows.SetProxy(strProxy, "", 4);
} }
+7 -3
View File
@@ -22,7 +22,7 @@ public class HttpClientHelper
this.httpClient = httpClient; this.httpClient = httpClient;
} }
public async Task<string?> TryGetAsync(string url) public async Task<string?> TryGetAsync(string url, CancellationToken cancellationToken = default)
{ {
if (url.IsNullOrEmpty()) if (url.IsNullOrEmpty())
{ {
@@ -31,8 +31,12 @@ public class HttpClientHelper
try try
{ {
var response = await httpClient.GetAsync(url); var response = await httpClient.GetAsync(url, cancellationToken);
return await response.Content.ReadAsStringAsync(); return await response.Content.ReadAsStringAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
} }
catch catch
{ {
+16 -3
View File
@@ -75,15 +75,28 @@ public sealed class SQLiteHelper
public async Task DisposeDbConnectionAsync() public async Task DisposeDbConnectionAsync()
{ {
await Task.Factory.StartNew(() => await Task.Run(() =>
{
try
{ {
_db?.Close(); _db?.Close();
_db?.Dispose(); _db?.Dispose();
}
finally
{
_db = null; _db = null;
}
_dbAsync?.GetConnection()?.Close(); try
_dbAsync?.GetConnection()?.Dispose(); {
var conn = _dbAsync?.GetConnection();
conn?.Close();
conn?.Dispose();
}
finally
{
_dbAsync = null; _dbAsync = null;
}
}); });
} }
} }
+1 -1
View File
@@ -229,7 +229,7 @@ public sealed class CoreInfoManager
DownloadUrlLinuxLoong64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-loong64.tar.gz", DownloadUrlLinuxLoong64 = urlSingbox + "/download/{0}/sing-box-{1}-linux-loong64.tar.gz",
DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz", DownloadUrlOSX64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-amd64.tar.gz",
DownloadUrlOSXArm64 = urlSingbox + "/download/{0}/sing-box-{1}-darwin-arm64.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", Match = "sing-box",
VersionArg = "version", VersionArg = "version",
}, },
+43 -58
View File
@@ -2,36 +2,37 @@ namespace ServiceLib.Manager;
public class PacManager public class PacManager
{ {
private static readonly Lazy<PacManager> _instance = new(() => new PacManager()); private const string Tag = "PacManager";
public static PacManager Instance => _instance.Value; private CancellationTokenSource? _cts;
private int _httpPort;
private int _pacPort; private int _pacPort;
private TcpListener? _tcpListener; private TcpListener? _tcpListener;
private byte[] _writeContent; private byte[] _writeContent = [];
private bool _isRunning;
private bool _needRestart = true;
public async Task StartAsync(int httpPort, int pacPort) public async Task StartAsync(int httpPort, int pacPort)
{ {
_needRestart = httpPort != _httpPort || pacPort != _pacPort || !_isRunning; var content = await InitText(httpPort);
_writeContent = content;
_httpPort = httpPort; if (_tcpListener is not null && _pacPort == pacPort)
_pacPort = pacPort;
await InitText();
if (_needRestart)
{ {
return;
}
Stop(); Stop();
RunListener(); 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 customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem.CustomSystemProxyPacPath;
var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath)) var fileName = customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath)
? customSystemProxyPacPath ? customSystemProxyPacPath
: Path.Combine(Utils.GetConfigPath(), "pac.txt"); : Path.Combine(Utils.GetConfigPath(), "pac.txt");
@@ -45,7 +46,7 @@ public class PacManager
} }
var pacText = await File.ReadAllTextAsync(fileName); 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(); var sb = new StringBuilder();
sb.AppendLine("HTTP/1.0 200 OK"); sb.AppendLine("HTTP/1.0 200 OK");
@@ -54,59 +55,43 @@ public class PacManager
sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText)); sb.AppendLine("Content-Length:" + Encoding.UTF8.GetByteCount(pacText));
sb.AppendLine(); sb.AppendLine();
sb.Append(pacText); 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 () =>
{
while (_isRunning)
{ {
var buffer = new byte[1024];
try try
{ {
if (!_tcpListener.Pending()) while (!token.IsCancellationRequested)
{ {
await Task.Delay(10); using var client = await listener.AcceptTcpClientAsync(token).ConfigureAwait(false);
continue; 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);
} }
var client = await _tcpListener.AcceptTcpClientAsync();
await Task.Run(() => WriteContent(client));
} }
catch catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
// ignored
} }
} catch (Exception ex)
}, TaskCreationOptions.LongRunning);
}
private void WriteContent(TcpClient client)
{ {
var stream = client.GetStream(); Logging.SaveLog(Tag, ex);
stream.Write(_writeContent, 0, _writeContent.Length); }
stream.Flush(); finally
{
listener.Stop();
}
} }
public void Stop() public void Stop()
{ {
if (_tcpListener == null) _cts?.Cancel();
{ _tcpListener?.Stop();
return; _cts?.Dispose();
} _cts = null;
try _pacPort = 0;
{
_isRunning = false;
_tcpListener.Stop();
_tcpListener = null; _tcpListener = null;
} }
catch
{
// ignored
}
}
} }
+4 -10
View File
@@ -12,23 +12,17 @@ public class TaskManager
_config = config; _config = config;
_updateFunc = updateFunc; _updateFunc = updateFunc;
_ = Task.Factory.StartNew( Task.Run(ScheduledTasks);
ScheduledTasks,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
} }
private async Task ScheduledTasks() private async Task ScheduledTasks()
{ {
Logging.SaveLog("Setup Scheduled Tasks"); Logging.SaveLog("Setup Scheduled Tasks");
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
var numOfExecuted = 1; var numOfExecuted = 1;
while (true) while (await timer.WaitForNextTickAsync().ConfigureAwait(false))
{ {
//1 minute
await Task.Delay(1000 * 60);
//Execute once 1 minute //Execute once 1 minute
try try
{ {
@@ -140,7 +134,7 @@ public class TaskManager
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask); 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) foreach (var msg in msgs)
{ {
await _updateFunc?.Invoke(false, msg); await _updateFunc?.Invoke(false, msg);
@@ -242,9 +242,9 @@ public class WebDavItem
[Serializable] [Serializable]
public class CheckUpdateItem public class CheckUpdateItem
{ {
public bool CheckPreReleaseUpdate { get; set; }
public bool UpdateViaProxy { get; set; } = true; public bool UpdateViaProxy { get; set; } = true;
public List<string>? SelectedCoreTypes { get; set; } public List<string>? SelectedCoreTypes { get; set; }
public List<string>? CheckPreReleaseCoreTypes { get; set; }
} }
[Serializable] [Serializable]
@@ -24,6 +24,7 @@ public record CoreConfigContext
public bool IsWindows { get; init; } public bool IsWindows { get; init; }
public bool IsMacOS { 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 // 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. // 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 class Outbound4Sbox : BaseServer4Sbox
{ {
public string? server { get; set; }
public int? server_port { get; set; }
public List<string>? server_ports { get; set; } public List<string>? server_ports { get; set; }
public string? uuid { get; set; } public string? uuid { get; set; }
public string? security { get; set; } public string? security { get; set; }
@@ -171,6 +169,10 @@ public class Endpoints4Sbox : BaseServer4Sbox
public string? udp_timeout { get; set; } public string? udp_timeout { get; set; }
public int? workers { get; set; } public int? workers { get; set; }
public List<Peer4Sbox> peers { 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 public class Peer4Sbox
@@ -271,10 +273,8 @@ public class Server4Sbox : BaseServer4Sbox
public string? inet4_range { get; set; } public string? inet4_range { get; set; }
public string? inet6_range { get; set; } public string? inet6_range { get; set; }
public string? client_subnet { get; set; } public string? client_subnet { get; set; }
public string? server { get; set; }
public new string? domain_resolver { get; set; } public new string? domain_resolver { get; set; }
[JsonPropertyName("interface")] public string? Interface { get; set; } [JsonPropertyName("interface")] public string? Interface { get; set; }
public int? server_port { get; set; }
public string? path { get; set; } public string? path { get; set; }
public Headers4Sbox? headers { get; set; } public Headers4Sbox? headers { get; set; }
@@ -359,4 +359,6 @@ public abstract class BaseServer4Sbox : DialFields4Sbox
{ {
public string type { get; set; } public string type { get; set; }
public string tag { 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 string? autoOutboundsInterface { get; set; }
public List<string>? dns { get; set; } public List<string>? dns { get; set; }
public bool? autoSystemDnsToGateway { get; set; }
public List<string>? autoSystemWfpBlockLeak { get; set; }
} }
public class Sniffing4Ray public class Sniffing4Ray
@@ -315,6 +319,7 @@ public class StreamSettings4Ray
public GrpcSettings4Ray? grpcSettings { get; set; } public GrpcSettings4Ray? grpcSettings { get; set; }
public HysteriaSettings4Ray? hysteriaSettings { get; set; } public HysteriaSettings4Ray? hysteriaSettings { get; set; }
public MasqueSettings4Ray? masqueSettings { get; set; }
public object? finalmask { get; set; } public object? finalmask { get; set; }
@@ -443,6 +448,12 @@ public class HysteriaSettings4Ray
public string? auth { get; set; } public string? auth { get; set; }
} }
public class MasqueSettings4Ray
{
public string? path { get; set; }
public Dictionary<string, string>? headers { get; set; }
}
public class UdpHop4Ray public class UdpHop4Ray
{ {
public string? ports { get; set; } public string? ports { get; set; }
@@ -3,6 +3,8 @@ namespace ServiceLib.Models.Dto;
public partial class CheckUpdateModel : ReactiveObject public partial class CheckUpdateModel : ReactiveObject
{ {
public bool? IsSelected { get; set; } public bool? IsSelected { get; set; }
public bool IsCheckPreRelease { get; set; }
public bool ShowCheckPreRelease { get; set; }
public ECoreType? CoreType { get; set; } public ECoreType? CoreType { get; set; }
[Reactive] public partial string? Remarks { get; set; } [Reactive] public partial string? Remarks { get; set; }
public string? FileName { get; set; } public string? FileName { get; set; }
@@ -46,6 +46,10 @@ public record ProtocolExtraItem
public int? InsecureConcurrency { get; init; } public int? InsecureConcurrency { get; init; }
public bool? NaiveQuic { get; init; } public bool? NaiveQuic { get; init; }
// MASQUE
public string? MasquePath { get; init; }
public string? MasqueHeaders { get; init; }
// group profile // group profile
public string? GroupType { get; init; } public string? GroupType { get; init; }
public string? ChildItems { get; init; } public string? ChildItems { get; init; }
+19 -1
View File
@@ -789,6 +789,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Add [MASQUE] 的本地化字符串。
/// </summary>
public static string menuAddMasqueServer {
get {
return ResourceManager.GetString("menuAddMasqueServer", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Add [NaïveProxy] 的本地化字符串。 /// 查找类似 Add [NaïveProxy] 的本地化字符串。
/// </summary> /// </summary>
@@ -924,6 +933,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Check and Update 的本地化字符串。
/// </summary>
public static string menuCheckAndUpdate {
get {
return ResourceManager.GetString("menuCheckAndUpdate", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Only Check 的本地化字符串。 /// 查找类似 Only Check 的本地化字符串。
/// </summary> /// </summary>
@@ -2149,7 +2167,7 @@ namespace ServiceLib.Resx {
} }
/// <summary> /// <summary>
/// 查找类似 Not Support 的本地化字符串。 /// 查找类似 Not Supported 的本地化字符串。
/// </summary> /// </summary>
public static string MsgNotSupport { public static string MsgNotSupport {
get { get {
+6
View File
@@ -399,6 +399,9 @@
<data name="MsgServerTitle" xml:space="preserve"> <data name="MsgServerTitle" xml:space="preserve">
<value>Filter, press Enter to execute</value> <value>Filter, press Enter to execute</value>
</data> </data>
<data name="menuCheckAndUpdate" xml:space="preserve">
<value>Check and Update</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve"> <data name="menuCheckUpdate" xml:space="preserve">
<value>Check Update</value> <value>Check Update</value>
</data> </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"> <data name="TbSortingProcess" xml:space="preserve">
<value>Process</value> <value>Process</value>
</data> </data>
<data name="menuAddMasqueServer" xml:space="preserve">
<value>Add [MASQUE]</value>
</data>
</root> </root>
+8 -2
View File
@@ -399,8 +399,11 @@
<data name="MsgServerTitle" xml:space="preserve"> <data name="MsgServerTitle" xml:space="preserve">
<value>Фильтр, нажмите Enter для выполнения</value> <value>Фильтр, нажмите Enter для выполнения</value>
</data> </data>
<data name="menuCheckAndUpdate" xml:space="preserve">
<value>Проверить и обновить</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve"> <data name="menuCheckUpdate" xml:space="preserve">
<value>Обновить</value> <value>Проверить обновления</value>
</data> </data>
<data name="menuClose" xml:space="preserve"> <data name="menuClose" xml:space="preserve">
<value>Закрыть</value> <value>Закрыть</value>
@@ -1780,7 +1783,7 @@
<value>Доступна новая версия {0}: {1}</value> <value>Доступна новая версия {0}: {1}</value>
</data> </data>
<data name="menuCheckOnly" xml:space="preserve"> <data name="menuCheckOnly" xml:space="preserve">
<value>Проверить</value> <value>Только проверить</value>
</data> </data>
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Не поддерживается</value> <value>Не поддерживается</value>
@@ -1899,4 +1902,7 @@
<data name="TbSortingProcess" xml:space="preserve"> <data name="TbSortingProcess" xml:space="preserve">
<value>Процесс</value> <value>Процесс</value>
</data> </data>
<data name="menuAddMasqueServer" xml:space="preserve">
<value>Добавить сервер [MASQUE]</value>
</data>
</root> </root>
@@ -399,6 +399,9 @@
<data name="MsgServerTitle" xml:space="preserve"> <data name="MsgServerTitle" xml:space="preserve">
<value>过滤器,按回车执行</value> <value>过滤器,按回车执行</value>
</data> </data>
<data name="menuCheckAndUpdate" xml:space="preserve">
<value>检查并更新</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve"> <data name="menuCheckUpdate" xml:space="preserve">
<value>检查更新</value> <value>检查更新</value>
</data> </data>
@@ -1891,4 +1894,7 @@
<data name="TbSortingProcess" xml:space="preserve"> <data name="TbSortingProcess" xml:space="preserve">
<value>进程</value> <value>进程</value>
</data> </data>
<data name="menuAddMasqueServer" xml:space="preserve">
<value>添加 [MASQUE]</value>
</data>
</root> </root>
@@ -399,6 +399,9 @@
<data name="MsgServerTitle" xml:space="preserve"> <data name="MsgServerTitle" xml:space="preserve">
<value>過濾器,按 Enter 執行</value> <value>過濾器,按 Enter 執行</value>
</data> </data>
<data name="menuCheckAndUpdate" xml:space="preserve">
<value>檢查並更新</value>
</data>
<data name="menuCheckUpdate" xml:space="preserve"> <data name="menuCheckUpdate" xml:space="preserve">
<value>檢查更新</value> <value>檢查更新</value>
</data> </data>
-105
View File
@@ -300,40 +300,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE----- -----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----- -----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
@@ -732,20 +698,6 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
4uJEvlz36hz1 4uJEvlz36hz1
-----END CERTIFICATE----- -----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----- -----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
@@ -805,38 +757,6 @@ HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE----- -----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----- -----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
@@ -1189,31 +1109,6 @@ AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
RA+GsCyRxj3qrg+E RA+GsCyRxj3qrg+E
-----END CERTIFICATE----- -----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----- -----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
@@ -365,9 +365,16 @@ public partial class CoreConfigSingboxService
rule4ExpectedIPs = JsonUtils.DeepCopy(rule); rule4ExpectedIPs = JsonUtils.DeepCopy(rule);
rule4ExpectedIPs.geosite = regionGeosite; 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); AddRules(rule, item, directDnsList);
} }
@@ -63,7 +63,7 @@ public partial class CoreConfigSingboxService
} }
var tunInbound = JsonUtils.Deserialize<Inbound4Sbox>(EmbedUtils.GetEmbedText(Global.TunSingboxInboundFileName)) ?? new Inbound4Sbox { }; 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.mtu = _config.TunModeItem.Mtu;
tunInbound.auto_route = _config.TunModeItem.AutoRoute; tunInbound.auto_route = _config.TunModeItem.AutoRoute;
tunInbound.strict_route = _config.TunModeItem.StrictRoute; 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); var endpoint = JsonUtils.Deserialize<Endpoints4Sbox>(txtOutbound);
FillEndpoint(endpoint); FillEndpoint(endpoint);
@@ -350,13 +350,13 @@ public partial class CoreConfigSingboxService
{ {
var protocolExtra = _node.GetProtocolExtra(); 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]; endpoint.type = Global.ProtocolTypes[_node.ConfigType];
switch (_node.ConfigType) switch (_node.ConfigType)
{ {
case EConfigType.WireGuard: case EConfigType.WireGuard:
{ {
endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress)?.Select(s => s.Trim()).ToList() ?? ["172.16.0.2/32"];
var peer = new Peer4Sbox var peer = new Peer4Sbox
{ {
public_key = protocolExtra.WgPublicKey ?? string.Empty, public_key = protocolExtra.WgPublicKey ?? string.Empty,
@@ -371,6 +371,28 @@ public partial class CoreConfigSingboxService
endpoint.peers = [peer]; endpoint.peers = [peer];
break; 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) catch (Exception ex)
@@ -402,7 +424,7 @@ public partial class CoreConfigSingboxService
} }
} }
private void FillOutboundTls(Outbound4Sbox outbound) private void FillOutboundTls(BaseServer4Sbox server)
{ {
try try
{ {
@@ -476,7 +498,7 @@ public partial class CoreConfigSingboxService
{ {
tls.ech = ech; tls.ech = ech;
} }
outbound.tls = tls; server.tls = tls;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -67,12 +67,10 @@ public partial class CoreConfigV2rayService
var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First(); var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First();
tunInbound.settings.gateway = [address]; tunInbound.settings.gateway = [address];
// Route both families into the tunnel regardless of EnableIPv6Address. That option only // Without ::/0, IPv6 bypasses the tunnel. Skip it only when the host has no global IPv6
// controls whether the interface gets an IPv6 address; leaving ::/0 out of the routing // address and EnableIPv6Address is off; NAT66 hosts have only ULA addresses.
// table makes IPv6 follow the system default route and bypass the tunnel entirely. var routeIPv6 = _config.TunModeItem.EnableIPv6Address || context.HasGlobalIPv6Address;
// A host without a global IPv6 address is the exception: it has nothing to leak, tunInbound.settings.autoSystemRoutingTable = routeIPv6
// and IPv6 sent into the tunnel would have no way back out.
tunInbound.settings.autoSystemRoutingTable = context.HasGlobalIPv6Address
? ["0.0.0.0/0", "::/0"] ? ["0.0.0.0/0", "::/0"]
: ["0.0.0.0/0"]; : ["0.0.0.0/0"];
if (_config.TunModeItem.EnableIPv6Address == true) if (_config.TunModeItem.EnableIPv6Address == true)
@@ -90,6 +88,15 @@ public partial class CoreConfigV2rayService
// tunInbound.sniffing.routeOnly = inbound.sniffing.routeOnly; // tunInbound.sniffing.routeOnly = inbound.sniffing.routeOnly;
tunInbound.sniffing.routeOnly = true; 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 }) if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
{ {
var wholeInternet = IPNetwork2.Parse("0.0.0.0/0"); var wholeInternet = IPNetwork2.Parse("0.0.0.0/0");
@@ -99,7 +106,7 @@ public partial class CoreConfigV2rayService
.Where(x => x != null).ToList(); .Where(x => x != null).ToList();
var includeList = new List<IPNetwork2> { wholeInternet }; var includeList = new List<IPNetwork2> { wholeInternet };
var includeListV6 = context.HasGlobalIPv6Address var includeListV6 = routeIPv6
? new List<IPNetwork2> { wholeInternetV6 } ? new List<IPNetwork2> { wholeInternetV6 }
: new List<IPNetwork2>(); : new List<IPNetwork2>();
@@ -182,6 +182,12 @@ public partial class CoreConfigV2rayService
outboundSettings.version = 2; outboundSettings.version = 2;
break; break;
} }
case EConfigType.MASQUE:
{
outboundSettings.address = _node.Address;
outboundSettings.port = _node.Port;
break;
}
case EConfigType.WireGuard: case EConfigType.WireGuard:
{ {
var address = _node.Address; var address = _node.Address;
@@ -257,8 +263,13 @@ public partial class CoreConfigV2rayService
{ {
network = "hysteria"; network = "hysteria";
} }
else if (_node.ConfigType == EConfigType.MASQUE)
{
network = "masque";
}
streamSettings.network = network; streamSettings.network = network;
var transport = _node.GetTransportExtra(); var transport = _node.GetTransportExtra();
var protocolExtra = _node.GetProtocolExtra();
var host = string.Empty; var host = string.Empty;
var path = string.Empty; var path = string.Empty;
var kcpSeed = string.Empty; var kcpSeed = string.Empty;
@@ -502,7 +513,6 @@ public partial class CoreConfigV2rayService
break; break;
case "hysteria": case "hysteria":
var protocolExtra = _node.GetProtocolExtra();
var ports = protocolExtra?.Ports; var ports = protocolExtra?.Ports;
int? upMbps = protocolExtra?.UpMbps is { } su and >= 0 int? upMbps = protocolExtra?.UpMbps is { } su and >= 0
? su ? su
@@ -574,6 +584,32 @@ public partial class CoreConfigV2rayService
streamSettings.finalmask = hy2Finalmask; streamSettings.finalmask = hy2Finalmask;
break; 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: default:
// raw // raw
if (headerType == Global.RawHeaderHttp) if (headerType == Global.RawHeaderHttp)
+33 -12
View File
@@ -8,7 +8,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private readonly Config? _config = config; private readonly Config? _config = config;
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc; private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
private readonly Lock _runLock = new(); private readonly Lock _runLock = new();
private CancellationTokenSource? _runCts; private readonly List<CancellationTokenSource> _runCtsList = [];
private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize; private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize;
private readonly TimeSpan _delayInterval = TimeSpan.FromSeconds(config.SpeedTestItem.SpeedTestDelayInterval ?? 1); 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) lock (_runLock)
{ {
_runCts?.Cancel();
runCts = CancellationTokenSource.CreateLinkedTokenSource(ct); runCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
_runCts = runCts; _runCtsList.Add(runCts);
} }
return RunLoopAsync(actionType, selecteds, runCts); return RunLoopAsync(actionType, selecteds, runCts);
@@ -30,17 +28,30 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
public void ExitLoop() public void ExitLoop()
{ {
CancellationTokenSource? runCts; var counter = 0;
List<CancellationTokenSource> listToCancel;
lock (_runLock) 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); _ = UpdateFunc("", ResUI.SpeedtestingStop);
runCts.Cancel();
} }
} }
@@ -67,10 +78,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
lock (_runLock) lock (_runLock)
{ {
if (ReferenceEquals(_runCts, runCts)) _runCtsList.Remove(runCts);
{
_runCts = null;
}
} }
runCts.Dispose(); runCts.Dispose();
@@ -202,6 +210,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
var parallelOptions = new ParallelOptions var parallelOptions = new ParallelOptions
{ {
MaxDegreeOfParallelism = lst.Count,
CancellationToken = ct, CancellationToken = ct,
}; };
@@ -283,6 +292,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
var parallelOptions = new ParallelOptions var parallelOptions = new ParallelOptions
{ {
MaxDegreeOfParallelism = selecteds.Count,
CancellationToken = ct, CancellationToken = ct,
}; };
@@ -373,6 +383,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
var parallelOptions = new ParallelOptions var parallelOptions = new ParallelOptions
{ {
MaxDegreeOfParallelism = selecteds.Count,
CancellationToken = ct, CancellationToken = ct,
}; };
@@ -562,6 +573,10 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
await clientSocket.ConnectAsync(endPoint, linkedCts.Token).ConfigureAwait(false); await clientSocket.ConnectAsync(endPoint, linkedCts.Token).ConfigureAwait(false);
responseTime = (int)timer.ElapsedMilliseconds; responseTime = (int)timer.ElapsedMilliseconds;
} }
catch
{
// Ignore
}
finally finally
{ {
timer.Stop(); 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) private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
{ {
List<List<ServerTestItem>> lstTest = []; 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 lst1 = lstSelected.Where(t => t.CoreType == ECoreType.Xray).ToList();
var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList(); var lst2 = lstSelected.Where(t => t.CoreType == ECoreType.sing_box).ToList();
@@ -5,8 +5,7 @@ namespace ServiceLib.Services.Statistics;
public class StatisticsSingboxService public class StatisticsSingboxService
{ {
private readonly Config _config; private readonly Config _config;
private bool _exitFlag; private CancellationTokenSource? _cts;
private ClientWebSocket? webSocket;
private readonly Func<ServerSpeedItem, Task>? _updateFunc; private readonly Func<ServerSpeedItem, Task>? _updateFunc;
private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic"; private string Url => $"ws://{Global.Loopback}:{AppManager.Instance.StatePort2}/traffic";
private static readonly string _tag = "StatisticsSingboxService"; private static readonly string _tag = "StatisticsSingboxService";
@@ -15,40 +14,17 @@ public class StatisticsSingboxService
{ {
_config = config; _config = config;
_updateFunc = updateFunc; _updateFunc = updateFunc;
_exitFlag = false;
_ = Task.Factory.StartNew( Task.Run(Run);
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 { }
} }
public void Close() public void Close()
{ {
try try
{ {
_exitFlag = true; _cts?.Cancel();
if (webSocket != null) _cts?.Dispose();
{ _cts = null;
webSocket.Abort();
webSocket = null;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -58,53 +34,63 @@ public class StatisticsSingboxService
private async Task Run() private async Task Run()
{ {
await Init(); Close();
_cts = new CancellationTokenSource();
var token = _cts.Token;
while (!_exitFlag) while (!token.IsCancellationRequested)
{ {
await Task.Delay(1000);
try try
{ {
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box)) if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
{ {
await Task.Delay(1000, token).ConfigureAwait(false);
continue; continue;
} }
if (webSocket != null) using var ws = new ClientWebSocket();
{ await ws.ConnectAsync(new Uri(Url), token).ConfigureAwait(false);
if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed)
{
webSocket.Abort();
webSocket = null;
await Init();
continue;
}
if (webSocket.State != WebSocketState.Open)
{
continue;
}
var buffer = new byte[1024]; var buffer = new byte[1024];
var res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); while (ws.State == WebSocketState.Open
while (!res.CloseStatus.HasValue) && !token.IsCancellationRequested)
{ {
var result = Encoding.UTF8.GetString(buffer, 0, res.Count); var res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token);
if (result.IsNotEmpty()) if (res.MessageType == WebSocketMessageType.Close)
{ {
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);
}
var result = Encoding.UTF8.GetString(ms.ToArray());
if (!result.IsNotEmpty())
{
continue;
}
ParseOutput(result, out var up, out var down); ParseOutput(result, out var up, out var down);
await _updateFunc?.Invoke(new ServerSpeedItem() if (_updateFunc != null)
{
await _updateFunc.Invoke(new ServerSpeedItem
{ {
ProxyUp = (long)(up / 1000), ProxyUp = (long)(up / 1000),
ProxyDown = (long)(down / 1000) ProxyDown = (long)(down / 1000),
}); }).ConfigureAwait(false);
}
res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
} }
} }
} }
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
break;
}
catch catch
{ {
await Task.Delay(3000, token).ConfigureAwait(false);
} }
} }
} }
@@ -5,33 +5,42 @@ public class StatisticsXrayService
private const long linkBase = 1024; private const long linkBase = 1024;
private ServerSpeedItem _serverSpeedItem = new(); private ServerSpeedItem _serverSpeedItem = new();
private readonly Config _config; private readonly Config _config;
private bool _exitFlag; private CancellationTokenSource? _cts;
private readonly Func<ServerSpeedItem, Task>? _updateFunc; private readonly Func<ServerSpeedItem, Task>? _updateFunc;
private string Url => $"{Global.HttpProtocol}{Global.Loopback}:{AppManager.Instance.StatePort}/debug/vars"; 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) public StatisticsXrayService(Config config, Func<ServerSpeedItem, Task> updateFunc)
{ {
_config = config; _config = config;
_updateFunc = updateFunc; _updateFunc = updateFunc;
_exitFlag = false;
_ = Task.Factory.StartNew( Task.Run(Run);
Run,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
} }
public void Close() public void Close()
{ {
_exitFlag = true; try
{
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
} }
private async Task Run() 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 try
{ {
if (AppManager.Instance.RunningCoreType != ECoreType.Xray) if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
@@ -39,16 +48,20 @@ public class StatisticsXrayService
continue; continue;
} }
var result = await HttpClientHelper.Instance.TryGetAsync(Url); var result = await HttpClientHelper.Instance.TryGetAsync(Url, token);
if (result != null) if (result != null)
{ {
var server = ParseOutput(result) ?? new ServerSpeedItem(); var server = ParseOutput(result) ?? new ServerSpeedItem();
await _updateFunc?.Invoke(server); await _updateFunc!.Invoke(server);
} }
} }
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
break;
}
catch catch
{ {
// ignored await Task.Delay(3000, token).ConfigureAwait(false);
} }
} }
} }
+2 -1
View File
@@ -111,7 +111,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
return await CheckUpdateAsync(downloadHandle, type, checkPreRelease, blProxy, cancellationToken); 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>(); var msgs = new List<string>();
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes()) foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
@@ -121,6 +121,7 @@ public partial class UpdateService(Config config, Func<bool, string, Task> updat
continue; continue;
} }
var preRelease = _config.CheckUpdateItem.CheckPreReleaseCoreTypes?.Contains(type.ToString()) ?? false;
var result = await CheckHasUpdateOnly(type, preRelease, blProxy, cancellationToken); var result = await CheckHasUpdateOnly(type, preRelease, blProxy, cancellationToken);
if (result.Success && result.Version != null) if (result.Success && result.Version != null)
{ {
@@ -97,6 +97,12 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
[Reactive] [Reactive]
public partial int GeckoMaxPacketSize { get; set; } public partial int GeckoMaxPacketSize { get; set; }
[Reactive]
public partial string MasquePath { get; set; }
[Reactive]
public partial string MasqueHeaders { get; set; }
[Reactive] [Reactive]
public partial string RawHeaderType { get; set; } public partial string RawHeaderType { get; set; }
@@ -322,6 +328,8 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty; Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty;
GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt(); GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt(); GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
MasquePath = protocolExtra.MasquePath ?? string.Empty;
MasqueHeaders = protocolExtra.MasqueHeaders ?? string.Empty;
RawHeaderType = transport.RawHeaderType ?? Global.None; RawHeaderType = transport.RawHeaderType ?? Global.None;
Host = transport.Host ?? string.Empty; Host = transport.Host ?? string.Empty;
@@ -443,6 +451,8 @@ public partial class AddServerViewModel : MyReactiveObject, ICloseable
Hy2RealmUrl = realm?.ToUri().NullIfEmpty(), Hy2RealmUrl = realm?.ToUri().NullIfEmpty(),
GeckoMinPacketSize = GeckoMinPacketSize > 0 ? GeckoMinPacketSize.ToString() : null, GeckoMinPacketSize = GeckoMinPacketSize > 0 ? GeckoMinPacketSize.ToString() : null,
GeckoMaxPacketSize = GeckoMaxPacketSize > 0 ? GeckoMaxPacketSize.ToString() : null, GeckoMaxPacketSize = GeckoMaxPacketSize > 0 ? GeckoMaxPacketSize.ToString() : null,
MasquePath = MasquePath.NullIfEmpty(),
MasqueHeaders = MasqueHeaders.NullIfEmpty(),
}); });
SelectedSource.SetTransportExtra(transport); SelectedSource.SetTransportExtra(transport);
@@ -12,7 +12,7 @@ public partial class CheckUpdateViewModel : MyReactiveObject
public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = []; public BulkObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = [];
public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; } public ReactiveCommand<RxVoid, RxVoid> CheckUpdateCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; } public ReactiveCommand<RxVoid, RxVoid> CheckOnlyCmd { get; }
[Reactive] public partial bool EnableCheckPreReleaseUpdate { get; set; }
[Reactive] public partial bool EnableUpdateViaProxy { get; set; } [Reactive] public partial bool EnableUpdateViaProxy { get; set; }
public CheckUpdateViewModel() public CheckUpdateViewModel()
@@ -33,20 +33,26 @@ public partial class CheckUpdateViewModel : MyReactiveObject
_ = UpdateView(_v2rayN, ex.Message); _ = UpdateView(_v2rayN, ex.Message);
}); });
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
EnableUpdateViaProxy = _config.CheckUpdateItem.UpdateViaProxy; EnableUpdateViaProxy = _config.CheckUpdateItem.UpdateViaProxy;
this.WhenAnyValue(x => x.EnableCheckPreReleaseUpdate)
.SubscribeAsync(async _ => await OnCheckPreReleaseUpdateChanged());
this.WhenAnyValue(x => x.EnableUpdateViaProxy) this.WhenAnyValue(x => x.EnableUpdateViaProxy)
.Subscribe(c => _ = OnUpdateViaProxyChanged()); .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() var models = CoreInfoManager.Instance.GetCheckUpdateCoreTypes()
.Select(t => GetCheckUpdateModel(t)) .Select(t => GetCheckUpdateModel(t))
.ToList(); .ToList();
@@ -63,6 +69,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
return new() return new()
{ {
IsSelected = false, IsSelected = false,
IsCheckPreRelease = false,
ShowCheckPreRelease = false,
CoreType = coreType, CoreType = coreType,
IsGeoFile = false, IsGeoFile = false,
Remarks = ResUI.menuCheckUpdate + $" ({ResUI.MsgNotSupport})", Remarks = ResUI.menuCheckUpdate + $" ({ResUI.MsgNotSupport})",
@@ -73,6 +81,8 @@ public partial class CheckUpdateViewModel : MyReactiveObject
return new() return new()
{ {
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType.ToString()) ?? true, IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType.ToString()) ?? true,
IsCheckPreRelease = _config.CheckUpdateItem.CheckPreReleaseCoreTypes?.Contains(coreType.ToString()) ?? false,
ShowCheckPreRelease = CoreInfoManager.Instance.GetCheckPreRelease(coreType, true),
CoreType = coreType, CoreType = coreType,
IsGeoFile = false, IsGeoFile = false,
Remarks = lastResult ?? ResUI.menuCheckUpdate, Remarks = lastResult ?? ResUI.menuCheckUpdate,
@@ -84,22 +94,14 @@ public partial class CheckUpdateViewModel : MyReactiveObject
return new() return new()
{ {
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(_geo) ?? true, IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(_geo) ?? true,
IsCheckPreRelease = false,
ShowCheckPreRelease = false,
CoreType = null, CoreType = null,
IsGeoFile = true, IsGeoFile = true,
Remarks = ResUI.menuCheckUpdate, Remarks = ResUI.menuCheckUpdate,
}; };
} }
private async Task OnCheckPreReleaseUpdateChanged()
{
if (_config.CheckUpdateItem.CheckPreReleaseUpdate == EnableCheckPreReleaseUpdate)
{
return;
}
_config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate;
await SaveSelectedCoreTypes();
}
private async Task OnUpdateViaProxyChanged() private async Task OnUpdateViaProxyChanged()
{ {
if (_config.CheckUpdateItem.UpdateViaProxy == EnableUpdateViaProxy) if (_config.CheckUpdateItem.UpdateViaProxy == EnableUpdateViaProxy)
@@ -117,6 +119,11 @@ public partial class CheckUpdateViewModel : MyReactiveObject
.Select(t => t.CoreTypeForStorage) .Select(t => t.CoreTypeForStorage)
.ToList(); .ToList();
_config.CheckUpdateItem.CheckPreReleaseCoreTypes =
CheckUpdateModels.Where(t => t.IsCheckPreRelease == true)
.Select(t => t.CoreTypeForStorage)
.ToList();
await ConfigHandler.SaveConfig(_config); 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 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) if (result.Success && result.Version != null)
{ {
await UpdateView(item.CoreType, string.Format(ResUI.MsgCheckUpdateHasNewVersion, item.CoreType, result.Version)); 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); await UpdateView(_v2rayN, ResUI.MsgNotSupport);
continue; continue;
} }
await CheckUpdateN(EnableCheckPreReleaseUpdate); await CheckUpdateN(item.IsCheckPreRelease);
}
else if (item.CoreType is ECoreType.Xray or ECoreType.sing_box)
{
await CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
} }
else if (item.CoreType.HasValue) 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); cancelDisposable.DisposeWith(disposables);
var token = cancelDisposable.Token; var token = cancelDisposable.Token;
Task.Factory.StartNew( Task.Run(() => GetClashConnectionsTask(token));
async () => await GetClashConnectionsTask(token),
token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default
);
}); });
} }
@@ -129,9 +124,9 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
try try
{ {
var numOfExecuted = 1; 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++; numOfExecuted++;
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
AppManager.Instance.IsRunningCore(ECoreType.sing_box))) AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
@@ -61,12 +61,7 @@ public partial class ClashProxiesViewModel : MyReactiveObject
cancelDisposable.DisposeWith(disposables); cancelDisposable.DisposeWith(disposables);
var token = cancelDisposable.Token; var token = cancelDisposable.Token;
Task.Factory.StartNew( Task.Run(() => GetClashProxiesTask(token));
async () => await GetClashProxiesTask(token),
token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default
);
}); });
} }
@@ -113,9 +108,9 @@ public partial class ClashProxiesViewModel : MyReactiveObject
try try
{ {
var numOfExecuted = 1; 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++; numOfExecuted++;
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
AppManager.Instance.IsRunningCore(ECoreType.sing_box))) 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> AddWireguardServerCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> AddAnytlsServerCmd { get; } public ReactiveCommand<RxVoid, RxVoid> AddAnytlsServerCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> AddNaiveServerCmd { get; } public ReactiveCommand<RxVoid, RxVoid> AddNaiveServerCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> AddMasqueServerCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> AddCustomServerCmd { get; } public ReactiveCommand<RxVoid, RxVoid> AddCustomServerCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> AddCustomOutboundServerCmd { get; } public ReactiveCommand<RxVoid, RxVoid> AddCustomOutboundServerCmd { get; }
public ReactiveCommand<RxVoid, RxVoid> AddPolicyGroupServerCmd { get; } public ReactiveCommand<RxVoid, RxVoid> AddPolicyGroupServerCmd { get; }
@@ -140,6 +141,10 @@ public partial class MainWindowViewModel : MyReactiveObject
{ {
await AddServerAsync(EConfigType.Naive); await AddServerAsync(EConfigType.Naive);
}); });
AddMasqueServerCmd = ReactiveCommand.CreateFromTask(async () =>
{
await AddServerAsync(EConfigType.MASQUE);
});
AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () => AddCustomServerCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await AddServerAsync(EConfigType.Custom); await AddServerAsync(EConfigType.Custom);
@@ -522,7 +527,7 @@ public partial class MainWindowViewModel : MyReactiveObject
public async Task AddServerViaImageAsync() public async Task AddServerViaImageAsync()
{ {
var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default); var imageFileName = await BrowseImageFileInteraction.HandleSafe(RxVoid.Default);
await AddScanResultAsync(imageFileName); await ScanImageResult(imageFileName);
} }
public async Task ScanImageResult(string fileName) 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 CoreType6 { get; set; }
[Reactive] public partial string CoreType7 { get; set; } [Reactive] public partial string CoreType7 { get; set; }
[Reactive] public partial string CoreType9 { get; set; } [Reactive] public partial string CoreType9 { get; set; }
[Reactive] public partial string CoreType14 { get; set; }
#endregion CoreType #endregion CoreType
@@ -283,6 +284,10 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
case 9: case 9:
CoreType9 = type; CoreType9 = type;
break; break;
case 14:
CoreType14 = type;
break;
} }
}); });
await Task.CompletedTask; await Task.CompletedTask;
@@ -451,6 +456,10 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable
type = CoreType9; type = CoreType9;
break; break;
case 14:
type = CoreType14;
break;
default: default:
continue; continue;
} }
@@ -219,7 +219,8 @@ public partial class StatusBarViewModel : MyReactiveObject
await ConfigHandler.InitBuiltinRouting(_config); await ConfigHandler.InitBuiltinRouting(_config);
await RefreshRoutingsMenu(); await RefreshRoutingsMenu();
await InboundDisplayStatus(); 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; BlRouting = true;
} }
@@ -24,12 +24,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -23,12 +23,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -24,12 +24,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -783,6 +783,70 @@
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />
</Grid> </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 <Separator
x:Name="sepa2" x:Name="sepa2"
Grid.Row="3" 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.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(currentTypeDisposables);
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables); this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables);
break; 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); .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.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });
} }
private void InitializeData(ProfileItem profileItem) private void InitializeData(ProfileItem profileItem)
@@ -284,6 +290,14 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls; cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
break; break;
case EConfigType.MASQUE:
gridMasque.IsVisible = true;
sepa2.IsVisible = false;
gridTransport.IsVisible = false;
cmbFingerprint.IsEnabled = false;
cmbAlpn.IsEnabled = false;
break;
} }
cmbStreamSecurity.ItemsSource = lstStreamSecurity; cmbStreamSecurity.ItemsSource = lstStreamSecurity;
cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity; cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity;
@@ -24,16 +24,23 @@
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel Grid.IsSharedSizeScope="True">
<Border <Border
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Theme="{DynamicResource CardBorder}"> Theme="{DynamicResource CardBorder}">
<Grid <Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto">
Margin="{StaticResource Margin4}" <Grid.ColumnDefinitions>
ColumnDefinitions="300,200" <ColumnDefinition
RowDefinitions="Auto,Auto,Auto,Auto"> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
@@ -79,10 +86,17 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Theme="{DynamicResource CardBorder}"> Theme="{DynamicResource CardBorder}">
<Grid <Grid Margin="{StaticResource Margin4}" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
Margin="{StaticResource Margin4}" <Grid.ColumnDefinitions>
ColumnDefinitions="300,200" <ColumnDefinition
RowDefinitions="Auto,Auto,Auto,Auto,Auto"> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions>
<StackPanel <StackPanel
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
@@ -35,27 +35,17 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" /> 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 <Button
x:Name="btnCheckOnly" x:Name="btnCheckOnly"
Width="100" MinWidth="100"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.menuCheckOnly}" /> Content="{x:Static resx:ResUI.menuCheckOnly}" />
<Button <Button
x:Name="btnCheckUpdate" x:Name="btnCheckUpdate"
Width="100" MinWidth="100"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.menuCheckUpdate}" /> Content="{x:Static resx:ResUI.menuCheckAndUpdate}" />
</StackPanel> </StackPanel>
</Border> </Border>
@@ -75,8 +65,8 @@
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Panel Width="500" VerticalAlignment="Center"> <Panel Width="680" VerticalAlignment="Center">
<Grid ColumnDefinitions="1*,1*,3*" RowDefinitions="Auto"> <Grid ColumnDefinitions="1*,3*,1*,3*" RowDefinitions="Auto">
<ToggleSwitch <ToggleSwitch
x:Name="togAutoRefresh" x:Name="togAutoRefresh"
Grid.Column="0" Grid.Column="0"
@@ -84,13 +74,32 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsSelected}" /> IsChecked="{Binding IsSelected}" />
<TextBlock
<DockPanel
Grid.Column="1" 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" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding CoreTypeForStorage}" /> Text="{Binding CoreTypeForStorage}" />
<TextBlock <TextBlock
Grid.Column="2" Grid.Column="3"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Remarks}" 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.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.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.CheckOnlyCmd, v => v.btnCheckOnly).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
@@ -24,12 +24,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -24,12 +24,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -23,17 +23,17 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnReset" x:Name="btnReset"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbReset}" /> Content="{x:Static resx:ResUI.TbReset}" />
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -18,6 +18,15 @@
ShowInTaskbar="True" ShowInTaskbar="True"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
mc:Ignorable="d"> 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"> <dialogHost:DialogHost CloseOnClickAway="True" DisableOpeningAnimation="True">
<DockPanel> <DockPanel>
<DockPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Top"> <DockPanel Margin="{StaticResource Margin8}" DockPanel.Dock="Top">
@@ -45,6 +54,7 @@
<MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" /> <MenuItem x:Name="menuAddTrojanServer" Header="{x:Static resx:ResUI.menuAddTrojanServer}" />
<MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" /> <MenuItem x:Name="menuAddHysteria2Server" Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem x:Name="menuAddWireguardServer" Header="{x:Static resx:ResUI.menuAddWireguardServer}" /> <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="menuAddSocksServer" Header="{x:Static resx:ResUI.menuAddSocksServer}" />
<MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" /> <MenuItem x:Name="menuAddHttpServer" Header="{x:Static resx:ResUI.menuAddHttpServer}" />
<Separator /> <Separator />
@@ -123,6 +133,7 @@
<TabControl <TabControl
x:Name="tabMain" x:Name="tabMain"
Grid.Column="2" Grid.Column="2"
Classes="logPane"
HorizontalContentAlignment="Left"> HorizontalContentAlignment="Left">
<TabItem x:Name="tabMsgView" Header="{x:Static resx:ResUI.MsgInformationTitle}" /> <TabItem x:Name="tabMsgView" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
<TabItem x:Name="tabClashProxies" Header="{x:Static resx:ResUI.TbProxies}" /> <TabItem x:Name="tabClashProxies" Header="{x:Static resx:ResUI.TbProxies}" />
@@ -138,6 +149,7 @@
<TabControl <TabControl
x:Name="tabMain1" x:Name="tabMain1"
Grid.Row="2" Grid.Row="2"
Classes="logPane"
TabStripPlacement="Left"> TabStripPlacement="Left">
<TabItem x:Name="tabMsgView1" Header="{x:Static resx:ResUI.MsgInformationTitle}" /> <TabItem x:Name="tabMsgView1" Header="{x:Static resx:ResUI.MsgInformationTitle}" />
<TabItem x:Name="tabClashProxies1" Header="{x:Static resx:ResUI.TbProxies}" /> <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.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).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.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.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomOutboundServerCmd, v => v.menuAddCustomOutboundServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomOutboundServerCmd, v => v.menuAddCustomOutboundServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.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.MsgViewModel, v => v.tabMsgView.Content).DisposeWith(currentLayoutDisposables);
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies.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.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.tabClashProxies.IsVisible).DisposeWith(currentLayoutDisposables);
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections.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); 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.MsgViewModel, v => v.tabMsgView1.Content).DisposeWith(currentLayoutDisposables);
this.OneWayBind(ViewModel, vm => vm.ClashProxiesViewModel, v => v.tabClashProxies1.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.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.tabClashProxies1.IsVisible).DisposeWith(currentLayoutDisposables);
this.OneWayBind(ViewModel, vm => vm.ShowClashUI, v => v.tabClashConnections1.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); this.Bind(ViewModel, vm => vm.TabMainSelectedIndex, v => v.tabMain1.SelectedIndex).DisposeWith(currentLayoutDisposables);
+2 -1
View File
@@ -23,7 +23,8 @@
Width="200" Width="200"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
PlaceholderText="{x:Static resx:ResUI.MsgFilterTitle}" /> PlaceholderText="{x:Static resx:ResUI.MsgFilterTitle}"
ToolTip.Tip="{x:Static resx:ResUI.MsgFilterTitle}" />
<Button <Button
x:Name="btnCopy" x:Name="btnCopy"
+8 -3
View File
@@ -47,9 +47,14 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
if (togScrollToEnd.IsChecked ?? true) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); txtMsg.ScrollToEnd();
Dispatcher.UIThread.Invoke(() => // Let layout finish without a nested dispatcher loop during a native window drag.
txtMsg.ScrollTo(txtMsg.LineCount, 0, AvaloniaEdit.Rendering.VisualYPosition.TextBottom, txtMsg.Bounds.Height, 0), Dispatcher.UIThread.Post(() =>
DispatcherPriority.Background); {
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"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -1192,7 +1192,7 @@
<Grid <Grid
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto" 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 <TextBlock
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
@@ -1296,6 +1296,19 @@
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" /> 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> </Grid>
</TabItem> </TabItem>
</TabControl> </TabControl>
@@ -46,6 +46,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType7.ItemsSource = Global.CoreTypes; cmbCoreType7.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes; cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbCoreType14.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(Global.SpeedTestConcurrencyCountMin, 20).ToList(); cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(Global.SpeedTestConcurrencyCountMin, 20).ToList();
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).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.CoreType6, v => v.cmbCoreType6.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.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.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); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });
@@ -28,12 +28,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -72,7 +72,8 @@
Margin="{StaticResource MarginLr4}" Margin="{StaticResource MarginLr4}"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Text="{Binding ServerFilter, Mode=TwoWay}" 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> </WrapPanel>
<!-- Profiles grid --> <!-- Profiles grid -->
@@ -66,7 +66,8 @@
Width="200" Width="200"
Margin="{StaticResource MarginLr4}" Margin="{StaticResource MarginLr4}"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
PlaceholderText="{x:Static resx:ResUI.MsgServerTitle}" /> PlaceholderText="{x:Static resx:ResUI.MsgServerTitle}"
ToolTip.Tip="{x:Static resx:ResUI.MsgServerTitle}" />
<Button <Button
x:Name="btnAutofitColumnWidth" x:Name="btnAutofitColumnWidth"
@@ -439,6 +439,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
private static readonly DataFormat<ProfileItemModel> LstProfilesRowFormat = private static readonly DataFormat<ProfileItemModel> LstProfilesRowFormat =
DataFormat.CreateInProcessFormat<ProfileItemModel>("LstProfilesRow"); DataFormat.CreateInProcessFormat<ProfileItemModel>("LstProfilesRow");
private (Point, PointerPressedEventArgs)? _dragStartPoint; private (Point, PointerPressedEventArgs)? _dragStartPoint;
private void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e) private void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e)
@@ -208,12 +208,12 @@
</StackPanel> </StackPanel>
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -30,12 +30,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -22,12 +22,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -136,7 +136,8 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}" /> Text="{x:Static resx:ResUI.LvAutoUpdateInterval}"
TextWrapping="Wrap" />
</DockPanel> </DockPanel>
<TextBlock <TextBlock
@@ -230,7 +231,8 @@
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}" /> PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}"
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
<Button <Button
x:Name="btnSelectPrevProfile" x:Name="btnSelectPrevProfile"
Grid.Row="10" Grid.Row="10"
@@ -250,7 +252,8 @@
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}" /> PlaceholderText="{x:Static resx:ResUI.LvPrevProfileTip}"
ToolTip.Tip="{x:Static resx:ResUI.LvPrevProfileTip}" />
<Button <Button
x:Name="btnSelectNextProfile" x:Name="btnSelectNextProfile"
Grid.Row="11" Grid.Row="11"
@@ -20,12 +20,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" /> IsDefault="True" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="True" /> IsCancel="True" />
@@ -10,6 +10,10 @@
<CETCompat>false</CETCompat> <CETCompat>false</CETCompat>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64' Or '$(RuntimeIdentifier)' == 'osx-arm64'">
<RuntimeHostConfigurationOption Include="System.Net.Security.UseNetworkFramework" Value="true" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.AvaloniaEdit" /> <PackageReference Include="Avalonia.AvaloniaEdit" />
<PackageReference Include="Avalonia.Controls.DataGrid"> <PackageReference Include="Avalonia.Controls.DataGrid">
@@ -27,13 +27,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
+2 -2
View File
@@ -25,13 +25,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
+85 -2
View File
@@ -33,13 +33,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -1000,6 +1000,89 @@
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />
</Grid> </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 <Separator
x:Name="sepa2" x:Name="sepa2"
Grid.Row="3" 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.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(currentTypeDisposables);
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables); this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables);
break; 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); .DisposeWith(disposables);
@@ -284,6 +291,14 @@ public partial class AddServerWindow
cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls; cmbCongestionControl12.ItemsSource = Global.NaiveCongestionControls;
break; 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.ItemsSource = lstStreamSecurity;
cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity; cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity;
+17 -5
View File
@@ -30,7 +30,7 @@
Style="{StaticResource ToolbarTextBlock}" /> Style="{StaticResource ToolbarTextBlock}" />
</DockPanel> </DockPanel>
<StackPanel> <StackPanel Grid.IsSharedSizeScope="True">
<materialDesign:Card Width="Auto" Margin="{StaticResource Margin8}"> <materialDesign:Card Width="Auto" Margin="{StaticResource Margin8}">
<Grid Margin="{StaticResource Margin8}"> <Grid Margin="{StaticResource Margin8}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -40,8 +40,14 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition
<ColumnDefinition Width="200" /> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
@@ -98,8 +104,14 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition
<ColumnDefinition Width="200" /> Width="Auto"
MinWidth="300"
SharedSizeGroup="BackupLabel" />
<ColumnDefinition
Width="Auto"
MinWidth="200"
SharedSizeGroup="BackupButton" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock
+30 -17
View File
@@ -14,7 +14,9 @@
x:TypeArguments="vms:CheckUpdateViewModel" x:TypeArguments="vms:CheckUpdateViewModel"
Style="{StaticResource ViewGlobal}" Style="{StaticResource ViewGlobal}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</UserControl.Resources>
<DockPanel Margin="{StaticResource Margin8}"> <DockPanel Margin="{StaticResource Margin8}">
<StackPanel <StackPanel
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
@@ -32,28 +34,18 @@
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" /> 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 <Button
x:Name="btnCheckOnly" x:Name="btnCheckOnly"
Width="100" MinWidth="100"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Content="{x:Static resx:ResUI.menuCheckOnly}" Content="{x:Static resx:ResUI.menuCheckOnly}"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCheckUpdate" x:Name="btnCheckUpdate"
Width="100" MinWidth="100"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Content="{x:Static resx:ResUI.menuCheckUpdate}" Content="{x:Static resx:ResUI.menuCheckAndUpdate}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
</StackPanel> </StackPanel>
@@ -71,7 +63,7 @@
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border <Border
Width="500" Width="660"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Padding="0" Padding="0"
VerticalAlignment="Center"> VerticalAlignment="Center">
@@ -81,6 +73,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" /> <ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" /> <ColumnDefinition Width="1*" />
<ColumnDefinition Width="3*" /> <ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -91,12 +84,32 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsSelected}" /> IsChecked="{Binding IsSelected}" />
<TextBlock
<DockPanel
Grid.Column="1" 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}" Style="{StaticResource ListItemTitle}"
Text="{Binding CoreTypeForStorage}" /> Text="{Binding CoreTypeForStorage}" />
<TextBlock <TextBlock
Grid.Column="2" Grid.Column="3"
VerticalAlignment="Center"
Style="{StaticResource ListItemSubTitle}" Style="{StaticResource ListItemSubTitle}"
Text="{Binding Remarks}" Text="{Binding Remarks}"
TextWrapping="WrapWithOverflow" /> TextWrapping="WrapWithOverflow" />
@@ -10,7 +10,6 @@ public partial class CheckUpdateView
{ {
this.OneWayBind(ViewModel, vm => vm.CheckUpdateModels, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables); 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.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.CheckOnlyCmd, v => v.btnCheckOnly).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
+2 -2
View File
@@ -25,13 +25,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -25,13 +25,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -25,19 +25,19 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnReset" x:Name="btnReset"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbReset}" Content="{x:Static resx:ResUI.TbReset}"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
+4
View File
@@ -112,6 +112,10 @@
x:Name="menuAddWireguardServer" x:Name="menuAddWireguardServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddWireguardServer}" /> Header="{x:Static resx:ResUI.menuAddWireguardServer}" />
<MenuItem
x:Name="menuAddMasqueServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddMasqueServer}" />
<MenuItem <MenuItem
x:Name="menuAddSocksServer" x:Name="menuAddSocksServer"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
+1
View File
@@ -46,6 +46,7 @@ public partial class MainWindow
this.BindCommand(ViewModel, vm => vm.AddWireguardServerCmd, v => v.menuAddWireguardServer).DisposeWith(disposables); 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.AddAnytlsServerCmd, v => v.menuAddAnytlsServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddNaiveServerCmd, v => v.menuAddNaiveServer).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.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomOutboundServerCmd, v => v.menuAddCustomOutboundServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddCustomOutboundServerCmd, v => v.menuAddCustomOutboundServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.AddPolicyGroupServerCmd, v => v.menuAddPolicyGroupServer).DisposeWith(disposables);
+2 -1
View File
@@ -28,7 +28,8 @@
materialDesign:TextFieldAssist.HasClearButton="True" materialDesign:TextFieldAssist.HasClearButton="True"
AutomationProperties.Name="{x:Static resx:ResUI.MsgFilterTitle}" AutomationProperties.Name="{x:Static resx:ResUI.MsgFilterTitle}"
IsEditable="True" IsEditable="True"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}"
ToolTip="{x:Static resx:ResUI.MsgFilterTitle}" />
<Button <Button
x:Name="btnCopy" x:Name="btnCopy"
Width="24" Width="24"
+18 -2
View File
@@ -32,13 +32,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -1473,6 +1473,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -1597,6 +1598,21 @@
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefComboBox}" /> 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> </Grid>
</TabItem> </TabItem>
</TabControl> </TabControl>
@@ -42,6 +42,7 @@ public partial class OptionSettingWindow
cmbCoreType6.ItemsSource = Global.CoreTypes; cmbCoreType6.ItemsSource = Global.CoreTypes;
cmbCoreType7.ItemsSource = Global.CoreTypes; cmbCoreType7.ItemsSource = Global.CoreTypes;
cmbCoreType9.ItemsSource = Global.CoreTypes; cmbCoreType9.ItemsSource = Global.CoreTypes;
cmbCoreType14.ItemsSource = Global.CoreTypes;
cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(Global.SpeedTestConcurrencyCountMin, 20).ToList(); cmbMixedConcurrencyCount.ItemsSource = Enumerable.Range(Global.SpeedTestConcurrencyCountMin, 20).ToList();
cmbSpeedTestTimeout.ItemsSource = Enumerable.Range(2, 5).Select(i => i * 5).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.CoreType6, v => v.cmbCoreType6.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType7, v => v.cmbCoreType7.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.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); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
}); });
@@ -30,13 +30,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -77,7 +77,8 @@
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}" materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
materialDesign:TextFieldAssist.HasClearButton="True" materialDesign:TextFieldAssist.HasClearButton="True"
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}" AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}"
ToolTip="{x:Static resx:ResUI.MsgServerTitle}" />
</WrapPanel> </WrapPanel>
<DataGrid <DataGrid
x:Name="lstProfiles" x:Name="lstProfiles"
@@ -149,5 +149,6 @@ public partial class ProfilesSelectWindow
e.Handled = true; e.Handled = true;
} }
} }
#endregion Event #endregion Event
} }
+2 -1
View File
@@ -65,7 +65,8 @@
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}" materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
materialDesign:TextFieldAssist.HasClearButton="True" materialDesign:TextFieldAssist.HasClearButton="True"
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}" AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}"
ToolTip="{x:Static resx:ResUI.MsgServerTitle}" />
<Button <Button
x:Name="btnAutofitColumnWidth" x:Name="btnAutofitColumnWidth"
@@ -237,13 +237,13 @@
</StackPanel> </StackPanel>
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -74,13 +74,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
+8 -5
View File
@@ -32,13 +32,13 @@
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnSave" x:Name="btnSave"
Width="100" MinWidth="100"
Content="{x:Static resx:ResUI.TbConfirm}" Content="{x:Static resx:ResUI.TbConfirm}"
IsDefault="True" IsDefault="True"
Style="{StaticResource DefButton}" /> Style="{StaticResource DefButton}" />
<Button <Button
x:Name="btnCancel" x:Name="btnCancel"
Width="100" MinWidth="100"
Margin="{StaticResource MarginLeftRight8}" Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.TbCancel}" Content="{x:Static resx:ResUI.TbCancel}"
IsCancel="true" IsCancel="true"
@@ -174,7 +174,8 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.LvAutoUpdateInterval}" /> Text="{x:Static resx:ResUI.LvAutoUpdateInterval}"
TextWrapping="Wrap" />
</DockPanel> </DockPanel>
<TextBlock <TextBlock
@@ -282,7 +283,8 @@
VerticalAlignment="Center" VerticalAlignment="Center"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}" materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
AcceptsReturn="True" AcceptsReturn="True"
Style="{StaticResource MyOutlinedTextBox}" /> Style="{StaticResource MyOutlinedTextBox}"
ToolTip="{x:Static resx:ResUI.LvPrevProfileTip}" />
<Button <Button
x:Name="btnSelectPrevProfile" x:Name="btnSelectPrevProfile"
Grid.Row="10" Grid.Row="10"
@@ -307,7 +309,8 @@
VerticalAlignment="Center" VerticalAlignment="Center"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}" materialDesign:HintAssist.Hint="{x:Static resx:ResUI.LvPrevProfileTip}"
AcceptsReturn="True" AcceptsReturn="True"
Style="{StaticResource MyOutlinedTextBox}" /> Style="{StaticResource MyOutlinedTextBox}"
ToolTip="{x:Static resx:ResUI.LvPrevProfileTip}" />
<Button <Button
x:Name="btnSelectNextProfile" x:Name="btnSelectNextProfile"
Grid.Row="11" Grid.Row="11"