Compare commits

..

27 Commits

Author SHA1 Message Date
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
89 changed files with 881 additions and 358 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.25.2</Version> <Version>7.25.5</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.72.16" />
<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" />
@@ -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()
{ {
@@ -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);
}
}
}
+25
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
+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
{ {
+20 -7
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(() =>
{ {
_db?.Close(); try
_db?.Dispose(); {
_db = null; _db?.Close();
_db?.Dispose();
}
finally
{
_db = null;
}
_dbAsync?.GetConnection()?.Close(); try
_dbAsync?.GetConnection()?.Dispose(); {
_dbAsync = null; var conn = _dbAsync?.GetConnection();
conn?.Close();
conn?.Dispose();
}
finally
{
_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",
}, },
+49 -64
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)
{ {
Stop(); return;
RunListener();
} }
Stop();
var cts = new CancellationTokenSource();
var listener = TcpListener.Create(pacPort);
listener.Start();
_cts = cts;
_pacPort = pacPort;
_tcpListener = listener;
_ = ListenLoopAsync(listener, cts.Token);
} }
private async Task InitText() private async Task<byte[]> InitText(int httpPort)
{ {
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath; var 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); var buffer = new byte[1024];
_isRunning = true; try
_tcpListener.Start();
Task.Factory.StartNew(async () =>
{ {
while (_isRunning) while (!token.IsCancellationRequested)
{ {
try using var client = await listener.AcceptTcpClientAsync(token).ConfigureAwait(false);
{ await using var stream = client.GetStream();
if (!_tcpListener.Pending()) _ = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
{ await stream.WriteAsync(_writeContent, token).ConfigureAwait(false);
await Task.Delay(10); await stream.FlushAsync(token).ConfigureAwait(false);
continue;
}
var client = await _tcpListener.AcceptTcpClientAsync();
await Task.Run(() => WriteContent(client));
}
catch
{
// ignored
}
} }
}, TaskCreationOptions.LongRunning); }
} catch (OperationCanceledException) when (token.IsCancellationRequested)
{
private void WriteContent(TcpClient client) }
{ catch (Exception ex)
var stream = client.GetStream(); {
stream.Write(_writeContent, 0, _writeContent.Length); Logging.SaveLog(Tag, ex);
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;
{ _tcpListener = null;
_isRunning = false;
_tcpListener.Stop();
_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>
@@ -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);
}
} }
else
if (rule.geosite?.Count > 0 || rule.domain?.Count > 0)
{ {
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)
{ {
@@ -90,6 +90,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");
@@ -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)
+27 -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();
@@ -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);
var buffer = new byte[1024];
while (ws.State == WebSocketState.Open
&& !token.IsCancellationRequested)
{ {
if (webSocket.State is WebSocketState.Aborted or WebSocketState.Closed) var res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token);
if (res.MessageType == WebSocketMessageType.Close)
{ {
webSocket.Abort(); break;
webSocket = null; }
await Init(); using var ms = new MemoryStream();
continue; ms.Write(buffer, 0, res.Count);
while (!res.EndOfMessage)
{
res = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), token).ConfigureAwait(false);
ms.Write(buffer, 0, res.Count);
} }
if (webSocket.State != WebSocketState.Open) var result = Encoding.UTF8.GetString(ms.ToArray());
if (!result.IsNotEmpty())
{ {
continue; continue;
} }
ParseOutput(result, out var up, out var down);
var buffer = new byte[1024]; if (_updateFunc != null)
var res = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!res.CloseStatus.HasValue)
{ {
var result = Encoding.UTF8.GetString(buffer, 0, res.Count); await _updateFunc.Invoke(new ServerSpeedItem
if (result.IsNotEmpty())
{ {
ParseOutput(result, out var up, out var down); ProxyUp = (long)(up / 1000),
ProxyDown = (long)(down / 1000),
await _updateFunc?.Invoke(new ServerSpeedItem() }).ConfigureAwait(false);
{
ProxyUp = (long)(up / 1000),
ProxyDown = (long)(down / 1000)
});
}
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);
@@ -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;
} }
@@ -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;
@@ -30,10 +30,11 @@
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 Width="Auto" MinWidth="300" />
RowDefinitions="Auto,Auto,Auto,Auto"> <ColumnDefinition Width="Auto" MinWidth="200" />
</Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
@@ -79,10 +80,11 @@
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 Width="Auto" MinWidth="300" />
RowDefinitions="Auto,Auto,Auto,Auto,Auto"> <ColumnDefinition Width="Auto" MinWidth="200" />
</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,31 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsSelected}" /> IsChecked="{Binding IsSelected}" />
<TextBlock
<StackPanel
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource Margin4}"
IsVisible="{Binding ShowCheckPreRelease}"
Orientation="Horizontal">
<ToggleSwitch
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
IsChecked="{Binding IsCheckPreRelease}"
OffContent=""
OnContent="" />
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
</StackPanel>
<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;
@@ -40,8 +40,8 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition Width="Auto" MinWidth="300" />
<ColumnDefinition Width="200" /> <ColumnDefinition Width="Auto" MinWidth="200" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
@@ -98,8 +98,8 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition Width="Auto" MinWidth="300" />
<ColumnDefinition Width="200" /> <ColumnDefinition Width="Auto" MinWidth="200" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock <TextBlock
+29 -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,31 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding IsSelected}" /> IsChecked="{Binding IsSelected}" />
<TextBlock
<StackPanel
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource MarginLeftRight4}"
Orientation="Horizontal"
Visibility="{Binding ShowCheckPreRelease, Converter={StaticResource BoolToVisConverter}}">
<ToggleButton
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
IsChecked="{Binding IsCheckPreRelease}" />
<TextBlock
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsEnableCheckPreReleaseUpdate}" />
</StackPanel>
<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"