Compare commits

...

27 commits

Author SHA1 Message Date
2dust
a74ca3f9d0 up 7.22.4
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
2026-05-29 11:08:39 +08:00
2dust
ed61f5c420 Rename ViewModel properties to PascalCase 2026-05-29 11:05:43 +08:00
2dust
bf98c4007f Use configurable page size and delay in speedtests
https://github.com/2dust/v2rayN/pull/9392
2026-05-29 10:37:00 +08:00
dependabot[bot]
78dcf51c3c
Bump actions/setup-dotnet from 5.2.0 to 5.3.0 (#9407)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.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-05-29 09:56:43 +08:00
2dust
1e59344074 Add balancer fallbackTag
https://github.com/2dust/v2rayN/issues/9401
2026-05-29 09:55:59 +08:00
JieXu
1d2442d58a
Fix the problem of font select (#9405)
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
* Update OptionSettingWindow.axaml.cs

* Update OptionSettingWindow.axaml.cs
2026-05-28 15:31:06 +08:00
2dust
3448782925 Remove unused C# style setting from .editorconfig 2026-05-28 14:31:27 +08:00
2dust
e6f4a57913 Limit update checks to selected core types
https://github.com/2dust/v2rayN/issues/9381
2026-05-28 14:31:01 +08:00
Roffild
4ae5c021fd
Add in Global.cs speed and ping test URLs (#9374)
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
* Add in Global.cs speed and ping test URLs

* 100mb

* Update Global.cs

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-05-27 16:54:26 +08:00
xihan123
0a357fd1a7
Change the SelectedValue bound to Text #9388 (#9389)
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
2026-05-26 20:20:15 +08:00
2dust
5ba5a805ce Make MTU combobox editable
Some checks failed
release Linux / build (push) Has been cancelled
release Linux / build and release deb x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm riscv64 (push) Has been cancelled
release Linux / build and release deb riscv64 (push) Has been cancelled
release Linux / build and release deb loong64 (push) Has been cancelled
release macOS / build (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (push) Has been cancelled
release Windows / build (push) Has been cancelled
release Linux / release-zip (push) Has been cancelled
release macOS / release-zip (push) Has been cancelled
release macOS / package and release macOS dmg (push) Has been cancelled
release Windows desktop (Avalonia UI) / release-zip (push) Has been cancelled
release Windows / release-zip (push) Has been cancelled
https://github.com/2dust/v2rayN/issues/9370
2026-05-24 14:54:06 +08:00
YFdyh000
807f0aba06
Make Admin AutoRun faster (#9366)
Some checks failed
release Linux / build (push) Has been cancelled
release Linux / build and release deb x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm riscv64 (push) Has been cancelled
release Linux / build and release deb riscv64 (push) Has been cancelled
release Linux / build and release deb loong64 (push) Has been cancelled
release macOS / build (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (push) Has been cancelled
release Windows / build (push) Has been cancelled
release Linux / release-zip (push) Has been cancelled
release macOS / release-zip (push) Has been cancelled
release macOS / package and release macOS dmg (push) Has been cancelled
release Windows desktop (Avalonia UI) / release-zip (push) Has been cancelled
release Windows / release-zip (push) Has been cancelled
* Set Admin AutoRun task priority to Normal rather than Below Normal

* Remove 30s delay for Admin AutoRun
2026-05-23 13:58:00 +08:00
2dust
f4a2086dfb Refine reload logic after RefreshServers
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
https://github.com/2dust/v2rayN/issues/9344
2026-05-22 10:07:36 +08:00
2dust
ccb0ffb3b6 up 7.22.3
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
2026-05-21 17:25:50 +08:00
2dust
1fa1246c0b Bug fix
https://github.com/2dust/v2rayN/issues/9351
2026-05-21 14:15:07 +08:00
2dust
14cc37d07a Add 'New Update' notification flow
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
Introduce a small update-notification feature: add AppEvents.HasUpdateNotified event and have TaskManager publish it when update messages exist. Add localized resource key (menuNewUpdate) and expose it in the ResUI designer; update resource files for several languages. In the UI, add a New Update button in MainWindow.xaml, wire its click to the existing check-update handler, bind its visibility to a new BlNewUpdate property on MainWindowViewModel, and subscribe the viewmodel to the new event. Also reset the notification flag after showing the Check Update dialog.
2026-05-21 11:25:49 +08:00
2dust
c7519f8ea7 Case-insensitive name checks and IpInfo visibility 2026-05-21 10:33:17 +08:00
2dust
0c796a157b Add IP info & flag emoji to test,add ip info column for main window
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
Fetch and display tested server IP and country (with emoji) in speed tests. Adds CountryExtension for country->emoji mapping and a new IpInfoResult type; ConnectionHandler now retrieves/parses IP API results and GetRealPingTime signature adjusted. Models and entities (ProfileItemModel, ProfileExItem, SpeedTestResult) gain IpInfo fields; ProfileExManager can store test IP info. UI/UX updated: new IpInfo column in ProfilesView (desktop and Avalonia), ResUI resource strings for "IP Info", and EServerColName ordering supports IpInfo. SpeedtestService now captures IP info and forwards it to the view model via the update function.
2026-05-20 19:16:58 +08:00
2dust
a9824fe6ec Improve ConnectionHandler DownloadService 2026-05-20 15:42:47 +08:00
2dust
f18758d4bf Support socks4/5 URIs and improve userinfo parsing 2026-05-20 14:28:01 +08:00
JieXu
c1a009a409
bump dependencies (#9341)
Some checks are pending
release Linux / build (push) Waiting to run
release Linux / release-zip (push) Blocked by required conditions
release Linux / build and release deb x64 & arm64 (push) Waiting to run
release Linux / build and release rpm x64 & arm64 (push) Waiting to run
release Linux / build and release rpm riscv64 (push) Waiting to run
release Linux / build and release deb riscv64 (push) Waiting to run
release Linux / build and release deb loong64 (push) Waiting to run
release macOS / build (push) Waiting to run
release macOS / release-zip (push) Blocked by required conditions
release macOS / package and release macOS dmg (push) Blocked by required conditions
release Windows desktop (Avalonia UI) / build (push) Waiting to run
release Windows desktop (Avalonia UI) / release-zip (push) Blocked by required conditions
release Windows / build (push) Waiting to run
release Windows / release-zip (push) Blocked by required conditions
* Update build-linux.yml

* Update Directory.Packages.props

* Update Directory.Packages.props
2026-05-20 09:01:22 +08:00
2dust
0f3fc8e053 up 7.22.2
Some checks failed
release Linux / build (push) Has been cancelled
release Linux / build and release deb x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm riscv64 (push) Has been cancelled
release Linux / build and release deb riscv64 (push) Has been cancelled
release Linux / build and release deb loong64 (push) Has been cancelled
release macOS / build (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (push) Has been cancelled
release Windows / build (push) Has been cancelled
release Linux / release-zip (push) Has been cancelled
release macOS / release-zip (push) Has been cancelled
release macOS / package and release macOS dmg (push) Has been cancelled
release Windows desktop (Avalonia UI) / release-zip (push) Has been cancelled
release Windows / release-zip (push) Has been cancelled
2026-05-18 17:36:05 +08:00
2dust
f7206f3405 Improve CheckUpdateModel 2026-05-18 17:32:07 +08:00
2dust
460a674ebc Add 'Check Only' update action
Some checks failed
release Linux / build (push) Has been cancelled
release Linux / build and release deb x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm x64 & arm64 (push) Has been cancelled
release Linux / build and release rpm riscv64 (push) Has been cancelled
release Linux / build and release deb riscv64 (push) Has been cancelled
release Linux / build and release deb loong64 (push) Has been cancelled
release macOS / build (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (push) Has been cancelled
release Windows / build (push) Has been cancelled
release Linux / release-zip (push) Has been cancelled
release macOS / release-zip (push) Has been cancelled
release macOS / package and release macOS dmg (push) Has been cancelled
release Windows desktop (Avalonia UI) / release-zip (push) Has been cancelled
release Windows / release-zip (push) Has been cancelled
Introduce a new "Check Only" feature: add CheckOnlyCmd to CheckUpdateViewModel with CheckOnlyTask that queries updates (via UpdateService.CheckHasUpdateOnly) for selected cores and reports results without performing updates. Wire up a new btnCheckOnly in both Desktop and Avalonia views and bind the command. Add localized menuCheckOnly entries to multiple .resx files and update ResUI.Designer. Also shorten the pre-release label to "Check for pre-release" in resource files.
2026-05-17 19:15:57 +08:00
DHR60
e2428f2500
Add tun inbound rule (#9327) 2026-05-17 18:54:12 +08:00
DHR60
bc3cbb4277
Fix (#9325)
* Fix

* Rename tun tag
2026-05-17 18:52:42 +08:00
2dust
ac9d0a0193 Add periodic update checks and core support 2026-05-17 17:09:34 +08:00
62 changed files with 975 additions and 299 deletions

View file

@ -100,7 +100,6 @@ csharp_style_prefer_tuple_swap = true:warning
csharp_style_prefer_utf8_string_literals = true:warning
csharp_style_throw_expression = true:warning
csharp_style_unused_value_assignment_preference = discard_variable:warning
csharp_style_unused_value_expression_statement_preference = discard_variable:warning
csharp_using_directive_placement = outside_namespace:warning
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:warning
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:warning

View file

@ -335,9 +335,9 @@ jobs:
env:
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }}
QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/debian13-loong64.qcow2
EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-code.fd
EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.4/edk2-loongarch64-vars.fd
QCOW2_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/debian13-loong64.qcow2
EFI_CODE_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-code.fd
EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-vars.fd
QCOW2_IMAGE: debian13-loong64.qcow2
EFI_CODE: edk2-loongarch64-code.fd
EFI_VARS: edk2-loongarch64-vars.fd

View file

@ -52,7 +52,7 @@ jobs:
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.2.0
uses: actions/setup-dotnet@v5.3.0
with:
dotnet-version: '10.0.1xx'

View file

@ -20,7 +20,7 @@ jobs:
fetch-depth: '0'
- name: Setup .NET
uses: actions/setup-dotnet@v5.2.0
uses: actions/setup-dotnet@v5.3.0
with:
dotnet-version: '8.0.x'

View file

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.22.1</Version>
<Version>7.22.4</Version>
</PropertyGroup>
<PropertyGroup>

View file

@ -7,8 +7,8 @@
<ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.16" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.16" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
@ -26,7 +26,7 @@
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
<PackageVersion Include="NLog" Version="6.1.3" />
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.1.4" />
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.1.7" />
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" />

View file

@ -0,0 +1,92 @@
namespace ServiceLib.Common;
/// <summary>
/// Extension methods for country code utilities
/// </summary>
public static class CountryExtension
{
/// <summary>
/// Country code to emoji flag mapping for common countries
/// </summary>
private static readonly Dictionary<string, string> CountryEmojiMap = new(StringComparer.OrdinalIgnoreCase)
{
// Asia
{ "CN", "🇨🇳" }, // China
{ "HK", "🇭🇰" }, // Hong Kong
{ "TW", "🇹🇼" }, // Taiwan
{ "JP", "🇯🇵" }, // Japan
{ "SG", "🇸🇬" }, // Singapore
{ "KR", "🇰🇷" }, // South Korea
{ "TH", "🇹🇭" }, // Thailand
{ "VN", "🇻🇳" }, // Vietnam
{ "ID", "🇮🇩" }, // Indonesia
{ "PH", "🇵🇭" }, // Philippines
{ "MY", "🇲🇾" }, // Malaysia
{ "IN", "🇮🇳" }, // India
{ "PK", "🇵🇰" }, // Pakistan
{ "BD", "🇧🇩" }, // Bangladesh
{ "LK", "🇱🇰" }, // Sri Lanka
{ "KH", "🇰🇭" }, // Cambodia
{ "LA", "🇱🇦" }, // Laos
{ "MM", "🇲🇲" }, // Myanmar
// Americas
{ "US", "🇺🇸" }, // United States
{ "CA", "🇨🇦" }, // Canada
{ "MX", "🇲🇽" }, // Mexico
{ "BR", "🇧🇷" }, // Brazil
{ "AR", "🇦🇷" }, // Argentina
{ "CL", "🇨🇱" }, // Chile
{ "CO", "🇨🇴" }, // Colombia
// Europe
{ "GB", "🇬🇧" }, // United Kingdom
{ "DE", "🇩🇪" }, // Germany
{ "FR", "🇫🇷" }, // France
{ "IT", "🇮🇹" }, // Italy
{ "ES", "🇪🇸" }, // Spain
{ "RU", "🇷🇺" }, // Russia
{ "NL", "🇳🇱" }, // Netherlands
{ "CH", "🇨🇭" }, // Switzerland
{ "SE", "🇸🇪" }, // Sweden
{ "NO", "🇳🇴" }, // Norway
{ "DK", "🇩🇰" }, // Denmark
{ "FI", "🇫🇮" }, // Finland
{ "PL", "🇵🇱" }, // Poland
{ "CZ", "🇨🇿" }, // Czech Republic
{ "AT", "🇦🇹" }, // Austria
{ "GR", "🇬🇷" }, // Greece
{ "PT", "🇵🇹" }, // Portugal
{ "TR", "🇹🇷" }, // Turkey
{ "UA", "🇺🇦" }, // Ukraine
{ "RO", "🇷🇴" }, // Romania
// Middle East & Central Asia
{ "AE", "🇦🇪" }, // United Arab Emirates
{ "SA", "🇸🇦" }, // Saudi Arabia
{ "IL", "🇮🇱" }, // Israel
{ "KZ", "🇰🇿" }, // Kazakhstan
// Oceania
{ "AU", "🇦🇺" }, // Australia
{ "NZ", "🇳🇿" }, // New Zealand
// Africa
{ "ZA", "🇿🇦" }, // South Africa
{ "EG", "🇪🇬" }, // Egypt
};
/// <summary>
/// Converts country code to flag emoji using predefined mapping
/// Example: "US" -> "🇺🇸", "CN" -> "🇨🇳"
/// </summary>
public static string? CountryToEmoji(this string? countryCode)
{
if (countryCode.IsNullOrEmpty())
{
return null;
}
return CountryEmojiMap.TryGetValue(countryCode, out var emoji) ? emoji : null;
}
}

View file

@ -12,6 +12,7 @@ public enum EServerColName
SubRemarks,
DelayVal,
SpeedVal,
IpInfo,
TodayDown,
TodayUp,

View file

@ -7,6 +7,7 @@ public static class AppEvents
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
public static readonly EventChannel<bool> HasUpdateNotified = new();
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();

View file

@ -55,7 +55,7 @@ public class Global
public const string DnsOutboundTag = "dns";
public const string DnsTag = "dns-module";
public const string DirectDnsTag = "direct-dns";
public const string BalancerTagSuffix = "-round";
public const string BalancerTagSuffix = "-balancer";
public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality";
public const string Loopback = "127.0.0.1";
@ -149,6 +149,9 @@ public class Global
public static readonly List<string> SpeedTestUrls =
[
@"https://cachefly.cachefly.net/50mb.test",
@"https://cachefly.cachefly.net/100mb.test",
@"https://cachefly.cachefly.net/1mb.test",
@"https://cachefly.cachefly.net/10mb.test",
@"https://speed.cloudflare.com/__down?bytes=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=99999999",
@ -157,6 +160,8 @@ public class Global
public static readonly List<string> SpeedPingTestUrls =
[
@"https://www.google.com/generate_204",
@"https://www.youtube.com/generate_204",
@"https://www.googlevideo.com/generate_204",
@"https://www.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt"
@ -207,6 +212,10 @@ public class Global
public const string NaiveQuicProtocolShare = "naive+quic://";
public const string SOCKS5Protocol = "socks5://";
public const string SOCKS4Protocol = "socks4://";
public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
{
{ EConfigType.VMess, "vmess://" },
@ -505,6 +514,7 @@ public class Global
public static readonly List<string> InboundTags =
[
"tun",
"socks",
"socks2",
"socks3"

View file

@ -111,7 +111,8 @@ public static class AutoStartupHandler
task.Settings.RunOnlyIfIdle = false;
task.Settings.IdleSettings.StopOnIdleEnd = false;
task.Settings.ExecutionTimeLimit = TimeSpan.Zero;
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser, Delay = TimeSpan.FromSeconds(30) });
task.Settings.Priority = ProcessPriorityClass.Normal;
task.Triggers.Add(new Microsoft.Win32.TaskScheduler.LogonTrigger { UserId = logonUser });
task.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName)));

View file

@ -923,6 +923,7 @@ public static class ConfigHandler
Delay = t33?.Delay ?? 0,
Speed = t33?.Speed ?? 0,
Sort = t33?.Sort ?? 0,
IpInfo = t33?.IpInfo ?? string.Empty,
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
TotalDown = (t22?.TotalDown ?? 0).ToString("D16"),
@ -943,6 +944,7 @@ public static class ConfigHandler
EServerColName.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(),
EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(),
EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).ToList(),
EServerColName.IpInfo => lstProfile.OrderBy(t => t.IpInfo).ToList(),
EServerColName.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(),
EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).ToList(),
@ -963,6 +965,7 @@ public static class ConfigHandler
EServerColName.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(),
EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(),
EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).ToList(),
EServerColName.IpInfo => lstProfile.OrderByDescending(t => t.IpInfo).ToList(),
EServerColName.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(),
EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).ToList(),

View file

@ -4,53 +4,41 @@ public static class ConnectionHandler
{
private static readonly string _tag = "ConnectionHandler";
/// <summary>
/// Runs ping and IP checks and returns a formatted result string.
/// </summary>
public static async Task<string> RunAvailabilityCheck()
{
var time = await GetRealPingTimeInfo();
var ip = time > 0 ? await GetIPInfo() ?? Global.None : Global.None;
var ip = time > 0 ? await GetIPInfo() : Global.None;
return string.Format(ResUI.TestMeOutput, time, ip);
}
/// <summary>
/// Gets IP information using the default local proxy.
/// </summary>
private static async Task<string?> GetIPInfo()
{
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
if (url.IsNullOrEmpty())
{
return null;
}
var webProxy = await GetWebProxy();
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, true, "");
if (result == null)
{
return null;
}
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
if (ipInfo == null)
{
return null;
}
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code;
return $"({country ?? "unknown"}) {ip}";
var ipInfo = await GetIPInfo(webProxy);
return ipInfo?.ToString() ?? Global.None;
}
/// <summary>
/// Measures real ping time using configured test URL.
/// </summary>
private static async Task<int> GetRealPingTimeInfo()
{
var responseTime = -1;
try
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
var webProxy = await GetWebProxy();
for (var i = 0; i < 2; i++)
{
responseTime = await GetRealPingTime(url, webProxy, 10);
responseTime = await GetRealPingTime(webProxy, 10);
if (responseTime > 0)
{
break;
@ -66,8 +54,21 @@ public static class ConnectionHandler
return responseTime;
}
public static async Task<int> GetRealPingTime(string url, IWebProxy? webProxy, int downloadTimeout)
/// <summary>
/// Creates local SOCKS proxy instance.
/// </summary>
private static async Task<WebProxy?> GetWebProxy()
{
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
return new WebProxy($"socks5://{Global.Loopback}:{port}");
}
/// <summary>
/// Measures response time by sending HTTP requests through proxy.
/// </summary>
public static async Task<int> GetRealPingTime(IWebProxy? webProxy, int downloadTimeout)
{
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
var responseTime = -1;
try
{
@ -95,4 +96,41 @@ public static class ConnectionHandler
}
return responseTime;
}
/// <summary>
/// Gets IP and country information through specified proxy.
/// </summary>
public static async Task<IpInfoResult?> GetIPInfo(IWebProxy? webProxy)
{
try
{
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl;
if (url.IsNullOrEmpty())
{
return null;
}
var downloadHandle = new DownloadService();
var result = await downloadHandle.TryDownloadString(url, webProxy, "");
if (result == null)
{
return null;
}
var ipInfo = JsonUtils.Deserialize<IPAPIInfo>(result);
if (ipInfo == null)
{
return null;
}
var ip = ipInfo.ip ?? ipInfo.clientIp ?? ipInfo.ip_addr ?? ipInfo.query;
var country = ipInfo.country_code ?? ipInfo.country ?? ipInfo.countryCode ?? ipInfo.location?.country_code ?? "unknown";
return new IpInfoResult(country, ip);
}
catch
{
return null;
}
}
}

View file

@ -53,7 +53,9 @@ public class FmtHandler
{
return ShadowsocksFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS]))
else if (str.StartsWith(Global.ProtocolShares[EConfigType.SOCKS])
|| str.StartsWith(Global.SOCKS5Protocol)
|| str.StartsWith(Global.SOCKS4Protocol))
{
return SocksFmt.Resolve(str, out msg);
}
@ -65,7 +67,8 @@ public class FmtHandler
{
return VLESSFmt.Resolve(str, out msg);
}
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2]) || str.StartsWith(Global.Hysteria2ProtocolShare))
else if (str.StartsWith(Global.ProtocolShares[EConfigType.Hysteria2])
|| str.StartsWith(Global.Hysteria2ProtocolShare))
{
return Hysteria2Fmt.Resolve(str, out msg);
}

View file

@ -99,12 +99,17 @@ public class SocksFmt : BaseFmt
};
// parse base64 UserInfo
var rawUserInfo = Utils.UrlDecode(parsedUrl.UserInfo);
var userInfo = Utils.Base64Decode(rawUserInfo);
var userInfoParts = userInfo.Split([':'], 2);
if (userInfoParts.Length == 2)
if (rawUserInfo.IsNotEmpty())
{
item.Username = userInfoParts.First();
item.Password = userInfoParts[1];
var userInfoParts = rawUserInfo.Contains(':')
? rawUserInfo.Split(":", 2)
: Utils.Base64Decode(rawUserInfo).Split(":", 2);
if (userInfoParts.Length == 2)
{
item.Username = userInfoParts.First();
item.Password = userInfoParts.Last();
}
}
return item;

View file

@ -48,6 +48,13 @@ public sealed class AppManager
}
}
public Dictionary<ECoreType, string> LastCheckUpdateResults { get; set; } = new();
public void SetLastCheckUpdateResult(ECoreType coreType, string result)
{
LastCheckUpdateResults[coreType] = result;
}
#endregion Property
#region App

View file

@ -50,6 +50,50 @@ public sealed class CoreInfoManager
return fileName;
}
public List<ECoreType> GetCheckUpdateCoreTypes()
{
var lst = new List<ECoreType>();
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
{
if (IsCheckUpdateSupported(ECoreType.v2rayN))
{
lst.Add(ECoreType.v2rayN);
}
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
{
lst.Add(ECoreType.Xray);
lst.Add(ECoreType.mihomo);
lst.Add(ECoreType.sing_box);
}
}
return lst;
}
public bool IsCheckUpdateSupported(ECoreType type)
{
return type switch
{
ECoreType.v2rayN => !Utils.IsPackagedInstall(),
ECoreType.Xray => true,
ECoreType.mihomo => true,
ECoreType.sing_box => true,
_ => false,
};
}
public bool GetCheckPreRelease(ECoreType type, bool preRelease)
{
return type switch
{
ECoreType.v2rayN => preRelease,
ECoreType.Xray => preRelease,
_ => false,
};
}
private void InitCoreInfo()
{
var urlN = GetCoreUrl(ECoreType.v2rayN);

View file

@ -150,6 +150,14 @@ public class ProfileExManager
IndexIdEnqueue(indexId);
}
public void SetTestIpInfo(string indexId, string ipInfo)
{
var profileEx = GetProfileExItem(indexId);
profileEx.IpInfo = ipInfo;
IndexIdEnqueue(indexId);
}
public void SetSort(string indexId, int sort)
{
var profileEx = GetProfileExItem(indexId);

View file

@ -70,6 +70,18 @@ public class TaskManager
}
}
//Execute once 24 hour
if (numOfExecuted % 1440 == 1)
{
try
{
await UpdateTaskRunCheckUpdate();
}
catch (Exception ex)
{
Logging.SaveLog("ScheduledTasks - UpdateTaskRunCheckUpdate", ex);
}
}
numOfExecuted++;
}
}
@ -117,4 +129,23 @@ public class TaskManager
}).UpdateGeoFileAll();
}
}
private async Task UpdateTaskRunCheckUpdate()
{
Logging.SaveLog("Execute check update");
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask);
var msgs = await updateService.CheckHasUpdateOnlyAll(_config.CheckUpdateItem.CheckPreReleaseUpdate);
foreach (var msg in msgs)
{
await _updateFunc?.Invoke(false, msg);
}
NoticeManager.Instance.Enqueue(string.Join("\n", msgs));
if (msgs.Count > 0)
{
AppEvents.HasUpdateNotified.Publish(true);
}
}
}

View file

@ -159,6 +159,8 @@ public class SpeedTestItem
public int MixedConcurrencyCount { get; set; }
public string IPAPIUrl { get; set; }
public string UdpTestTarget { get; set; }
public int? SpeedTestPageSize { get; set; }
public int? SpeedTestDelayInterval { get; set; }
}
[Serializable]

View file

@ -276,6 +276,7 @@ public class BalancersItem4Ray
public List<string>? selector { get; set; }
public BalancersStrategy4Ray? strategy { get; set; }
public string? tag { get; set; }
public string? fallbackTag { get; set; }
}
public class BalancersStrategy4Ray

View file

@ -3,8 +3,10 @@ namespace ServiceLib.Models.Dto;
public class CheckUpdateModel : ReactiveObject
{
public bool? IsSelected { get; set; }
public string? CoreType { get; set; }
public ECoreType? CoreType { get; set; }
[Reactive] public string? Remarks { get; set; }
public string? FileName { get; set; }
public bool? IsFinished { get; set; }
public bool IsGeoFile { get; set; }
public string CoreTypeForStorage => IsGeoFile ? "GeoFiles" : (CoreType?.ToString() ?? "");
}

View file

@ -17,3 +17,12 @@ public class LocationInfo
{
public string? country_code { get; set; }
}
public readonly record struct IpInfoResult(string Country, string? Ip)
{
public override string ToString()
{
var emoji = Country.CountryToEmoji();
return $"{emoji}({Country}) {Ip}";
}
}

View file

@ -26,6 +26,9 @@ public class ProfileItemModel : ReactiveObject
[Reactive]
public string SpeedVal { get; set; }
[Reactive]
public string IpInfo { get; set; }
[Reactive]
public string TodayUp { get; set; }

View file

@ -8,4 +8,6 @@ public class SpeedTestResult
public string? Delay { get; set; }
public string? Speed { get; set; }
public string? IpInfo { get; set; }
}

View file

@ -10,4 +10,5 @@ public class ProfileExItem
public decimal Speed { get; set; }
public int Sort { get; set; }
public string? Message { get; set; }
public string? IpInfo { get; set; }
}

View file

@ -564,6 +564,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 IP Info 的本地化字符串。
/// </summary>
public static string LvTestIpInfo {
get {
return ResourceManager.GetString("LvTestIpInfo", resourceCulture);
}
}
/// <summary>
/// 查找类似 Speed (MB/s) 的本地化字符串。
/// </summary>
@ -870,6 +879,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Only Check 的本地化字符串。
/// </summary>
public static string menuCheckOnly {
get {
return ResourceManager.GetString("menuCheckOnly", resourceCulture);
}
}
/// <summary>
/// 查找类似 Check Update 的本地化字符串。
/// </summary>
@ -1302,6 +1320,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 New Update 的本地化字符串。
/// </summary>
public static string menuNewUpdate {
get {
return ResourceManager.GetString("menuNewUpdate", resourceCulture);
}
}
/// <summary>
/// 查找类似 Open the storage location 的本地化字符串。
/// </summary>
@ -1887,6 +1914,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 {0} has a new version available: {1} 的本地化字符串。
/// </summary>
public static string MsgCheckUpdateHasNewVersion {
get {
return ResourceManager.GetString("MsgCheckUpdateHasNewVersion", resourceCulture);
}
}
/// <summary>
/// 查找类似 Core &apos;{0}&apos; does not support network type &apos;{1}&apos; 的本地化字符串。
/// </summary>
@ -2040,6 +2076,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Not Support 的本地化字符串。
/// </summary>
public static string MsgNotSupport {
get {
return ResourceManager.GetString("MsgNotSupport", resourceCulture);
}
}
/// <summary>
/// 查找类似 Not support protocol &apos;{0}&apos; 的本地化字符串。
/// </summary>
@ -3484,7 +3529,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。
/// 查找类似 tun: TUN inbound, socks: local port, socks2: second local port, socks3: LAN port 的本地化字符串。
/// </summary>
public static string TbRoutingInboundTagTips {
get {
@ -3925,7 +3970,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 Check for pre-release updates 的本地化字符串。
/// 查找类似 Check for pre-release 的本地化字符串。
/// </summary>
public static string TbSettingsEnableCheckPreReleaseUpdate {
get {

View file

@ -1731,4 +1731,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root>

View file

@ -1728,4 +1728,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root>

View file

@ -1731,4 +1731,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root>

View file

@ -691,7 +691,7 @@
<value>Automatically adjust column width after subscription update</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>Check for pre-release updates</value>
<value>Check for pre-release</value>
</data>
<data name="TbSettingsException" xml:space="preserve">
<value>Exception</value>
@ -1336,7 +1336,7 @@
<value>Enable second mixed port</value>
</data>
<data name="TbRoutingInboundTagTips" xml:space="preserve">
<value>socks: local port, socks2: second local port, socks3: LAN port</value>
<value>tun: TUN inbound, socks: local port, socks2: second local port, socks3: LAN port</value>
</data>
<data name="TbSettingsTheme" xml:space="preserve">
<value>Theme</value>
@ -1731,4 +1731,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Export v2rayN Internal Share Link to Clipboard</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root>

View file

@ -1731,4 +1731,19 @@
<data name="menuExport2InnerUri" xml:space="preserve">
<value>Экспорт внутренней ссылки v2rayN в буфер обмена</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} has a new version available: {1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>Only Check</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root>

View file

@ -691,7 +691,7 @@
<value>自动调整配置列宽在更新订阅后</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>检查 Pre-Release 更新 (请谨慎启用)</value>
<value>检查 Pre-Release</value>
</data>
<data name="TbSettingsException" xml:space="preserve">
<value>例外</value>
@ -1333,7 +1333,7 @@
<value>开启第二个本地监听端口</value>
</data>
<data name="TbRoutingInboundTagTips" xml:space="preserve">
<value>Socks本地端口Socks2第二个本地端口Socks3局域网端口</value>
<value>TunTUN 入站,Socks本地端口Socks2第二个本地端口Socks3局域网端口</value>
</data>
<data name="TbSettingsTheme" xml:space="preserve">
<value>主题</value>
@ -1728,4 +1728,19 @@
<data name="menuExport2InnerUri" xml:space="preserve">
<value>导出 v2rayN 内部分享链接至剪贴板 (多选)</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} 有新版本可用:{1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>仅检查</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>不支持</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP 信息</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
</root>

View file

@ -691,7 +691,7 @@
<value>在更新訂閱後自動調整列寬</value>
</data>
<data name="TbSettingsEnableCheckPreReleaseUpdate" xml:space="preserve">
<value>檢查 Pre-Release 更新 (請謹慎啟用)</value>
<value>檢查 Pre-Release</value>
</data>
<data name="TbSettingsException" xml:space="preserve">
<value>例外</value>
@ -1728,4 +1728,19 @@
<data name="menuExport2InnerUri" xml:space="preserve">
<value>匯出 v2rayN 內部分享連結至剪貼簿(多選)</value>
</data>
<data name="MsgCheckUpdateHasNewVersion" xml:space="preserve">
<value>{0} 有新版本可用:{1}</value>
</data>
<data name="menuCheckOnly" xml:space="preserve">
<value>僅檢查</value>
</data>
<data name="MsgNotSupport" xml:space="preserve">
<value>不支援</value>
</data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP 資訊</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
</root>

View file

@ -1,6 +1,6 @@
{
"type": "tun",
"tag": "tun-in",
"tag": "tun",
"interface_name": "singbox_tun",
"address": [
"172.18.0.1/30",

View file

@ -87,8 +87,14 @@ public partial class CoreConfigSingboxService
});
_coreConfig.route.rules.Add(new()
{
protocol = ["dns"],
action = "hijack-dns"
type = "logical",
mode = "or",
action = "hijack-dns",
rules =
[
new() { port = [53] },
new() { protocol = ["dns"] },
],
});
}
else
@ -96,7 +102,7 @@ public partial class CoreConfigSingboxService
_coreConfig.route.rules.Add(new()
{
port = [53],
action = "hijack-dns"
action = "hijack-dns",
});
}

View file

@ -104,6 +104,7 @@ public partial class CoreConfigV2rayService
},
},
tag = balancerTag,
fallbackTag = multipleLoad == EMultipleLoad.Fallback ? Global.DirectTag : null,
};
_coreConfig.routing.balancers ??= new();
_coreConfig.routing.balancers.Add(balancer);

View file

@ -27,6 +27,7 @@ public partial class CoreConfigV2rayService
});
_coreConfig.routing.rules.Add(new()
{
inboundTag = ["tun"],
port = "53",
outboundTag = Global.DnsOutboundTag,
});

View file

@ -3,7 +3,7 @@ using System.Net.Http.Headers;
namespace ServiceLib.Services;
/// <summary>
///Download
/// Download
/// </summary>
public class DownloadService
{
@ -13,7 +13,10 @@ public class DownloadService
private static readonly string _tag = "DownloadService";
public async Task<int> DownloadDataAsync(string url, WebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc)
/// <summary>
/// Downloads data with the specified proxy and reports progress messages.
/// </summary>
public async Task<int> DownloadDataAsync(string url, IWebProxy webProxy, int downloadTimeout, Func<bool, string, Task> updateFunc)
{
try
{
@ -36,6 +39,9 @@ public class DownloadService
return 0;
}
/// <summary>
/// Downloads a file and reports progress through events.
/// </summary>
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
{
try
@ -64,6 +70,9 @@ public class DownloadService
}
}
/// <summary>
/// Gets redirect target URL without following redirects automatically.
/// </summary>
public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
{
var webRequestHandler = new SocketsHttpHandler
@ -86,11 +95,23 @@ public class DownloadService
}
}
/// <summary>
/// Tries to download string content using proxy switch setting.
/// </summary>
public async Task<string?> TryDownloadString(string url, bool blProxy, string userAgent)
{
var webProxy = await GetWebProxy(blProxy);
return await TryDownloadString(url, webProxy, userAgent);
}
/// <summary>
/// Tries to download string content with a specified proxy.
/// </summary>
public async Task<string?> TryDownloadString(string url, IWebProxy? webProxy, string userAgent)
{
try
{
var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15);
var result1 = await DownloadStringAsync(url, webProxy, userAgent, 15);
if (result1.IsNotEmpty())
{
return result1;
@ -108,7 +129,7 @@ public class DownloadService
try
{
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15);
var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, 15);
if (result2.IsNotEmpty())
{
return result2;
@ -128,14 +149,12 @@ public class DownloadService
}
/// <summary>
/// DownloadString
/// Downloads string content via HttpClient.
/// </summary>
/// <param name="url"></param>
private async Task<string?> DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout)
private async Task<string?> DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, int timeout)
{
try
{
var webProxy = await GetWebProxy(blProxy);
var client = new HttpClient(new SocketsHttpHandler()
{
Proxy = webProxy,
@ -172,15 +191,12 @@ public class DownloadService
}
/// <summary>
/// DownloadString
/// Downloads string content via DownloaderHelper.
/// </summary>
/// <param name="url"></param>
private async Task<string?> DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout)
private async Task<string?> DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, int timeout)
{
try
{
var webProxy = await GetWebProxy(blProxy);
if (userAgent.IsNullOrEmpty())
{
userAgent = Utils.GetVersion(false);
@ -200,6 +216,9 @@ public class DownloadService
return null;
}
/// <summary>
/// Creates local SOCKS proxy when proxy switch is enabled.
/// </summary>
private async Task<WebProxy?> GetWebProxy(bool blProxy)
{
if (!blProxy)
@ -215,6 +234,9 @@ public class DownloadService
return new WebProxy($"socks5://{Global.Loopback}:{port}");
}
/// <summary>
/// Checks whether the specified TCP endpoint is reachable.
/// </summary>
private async Task<bool> SocketCheck(string ip, int port)
{
try

View file

@ -8,6 +8,8 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private readonly Config? _config = config;
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
private static readonly ConcurrentBag<string> _lstExitLoop = new();
private readonly int _speedTestPageSize = config.SpeedTestItem.SpeedTestPageSize ?? Global.SpeedTestPageSize;
private readonly TimeSpan _delayInterval = TimeSpan.FromSeconds(config.SpeedTestItem.SpeedTestDelayInterval ?? 1);
public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
{
@ -135,32 +137,39 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task RunTcpingAsync(List<ServerTestItem> selecteds)
{
List<Task> tasks = [];
foreach (var it in selecteds)
{
tasks.Add(Task.Run(async () =>
{
try
{
var responseTime = await GetTcpingTime(it.Address, it.Port);
var pageSize = Math.Min(selecteds.Count, _speedTestPageSize);
var lstBatch = GetTestBatchItem(selecteds, pageSize);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());
}
catch (Exception ex)
foreach (var lst in lstBatch)
{
List<Task> tasks = [];
foreach (var it in lst)
{
tasks.Add(Task.Run(async () =>
{
Logging.SaveLog(_tag, ex);
}
}));
try
{
var responseTime = await GetTcpingTime(it.Address, it.Port);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}));
}
await Task.WhenAll(tasks);
await Task.Delay(_delayInterval);
}
await Task.WhenAll(tasks);
}
private async Task RunRealPingBatchAsync(List<ServerTestItem> lstSelected, string exitLoopKey, int pageSize = 0)
{
if (pageSize <= 0)
{
pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize;
pageSize = Math.Min(lstSelected.Count, _speedTestPageSize);
}
var lstTest = GetTestBatchItem(lstSelected, pageSize);
@ -172,7 +181,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
lstFailed.AddRange(lst);
}
await Task.Delay(100);
await Task.Delay(_delayInterval);
}
//Retest the failed part
@ -249,7 +258,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
if (pageSize <= 0)
{
pageSize = lstSelected.Count < Global.SpeedTestPageSize ? lstSelected.Count : Global.SpeedTestPageSize;
pageSize = Math.Min(lstSelected.Count, _speedTestPageSize);
}
var lstTest = GetTestBatchItem(lstSelected, pageSize);
@ -261,7 +270,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{
lstFailed.AddRange(lst);
}
await Task.Delay(100);
await Task.Delay(_delayInterval);
}
//Retest the failed part
@ -392,10 +401,23 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<int> DoRealPing(ServerTestItem it)
{
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}");
var responseTime = await ConnectionHandler.GetRealPingTime(_config.SpeedTestItem.SpeedPingTestUrl, webProxy, 10);
var responseTime = await ConnectionHandler.GetRealPingTime(webProxy, 10);
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString());
if (responseTime > 0)
{
var ipInfo = await ConnectionHandler.GetIPInfo(webProxy);
var ipStr = ipInfo?.ToString() ?? Global.None;
ProfileExManager.Instance.SetTestIpInfo(it.IndexId, ipStr);
await UpdateIpInfoFunc(it.IndexId, ipStr);
}
else
{
await UpdateIpInfoFunc(it.IndexId, ResUI.SpeedtestingSkip);
}
return responseTime;
}
@ -491,4 +513,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
ProfileExManager.Instance.SetTestMessage(indexId, speed);
}
}
private async Task UpdateIpInfoFunc(string indexId, string ip)
{
await _updateFunc?.Invoke(new() { IndexId = indexId, IpInfo = ip });
}
}

View file

@ -100,6 +100,43 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
}
}
public async Task<UpdateResult> CheckHasUpdateOnly(ECoreType type, bool preRelease)
{
if (!CoreInfoManager.Instance.IsCheckUpdateSupported(type))
{
return new UpdateResult(false, ResUI.MsgNotSupport);
}
var downloadHandle = new DownloadService();
var checkPreRelease = CoreInfoManager.Instance.GetCheckPreRelease(type, preRelease);
return await CheckUpdateAsync(downloadHandle, type, checkPreRelease);
}
public async Task<List<string>> CheckHasUpdateOnlyAll(bool preRelease)
{
var msgs = new List<string>();
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
{
if (!(_config.CheckUpdateItem.SelectedCoreTypes?.Contains(type.ToString()) ?? true))
{
continue;
}
var result = await CheckHasUpdateOnly(type, preRelease);
if (result.Success && result.Version != null)
{
var msg = string.Format(ResUI.MsgCheckUpdateHasNewVersion, type, result.Version);
msgs.Add(msg);
AppManager.Instance.SetLastCheckUpdateResult(type, msg);
}
else
{
AppManager.Instance.SetLastCheckUpdateResult(type, result.Msg);
}
}
return msgs;
}
public async Task UpdateGeoFileAll()
{
await UpdateGeoFiles();

View file

@ -3,12 +3,13 @@ namespace ServiceLib.ViewModels;
public class CheckUpdateViewModel : MyReactiveObject
{
private const string _geo = "GeoFiles";
private readonly string _v2rayN = ECoreType.v2rayN.ToString();
private readonly ECoreType _v2rayN = ECoreType.v2rayN;
private List<CheckUpdateModel> _lstUpdated = [];
private static readonly string _tag = "CheckUpdateViewModel";
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
public ReactiveCommand<Unit, Unit> CheckOnlyCmd { get; }
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
@ -23,12 +24,19 @@ public class CheckUpdateViewModel : MyReactiveObject
_ = UpdateView(_v2rayN, ex.Message);
});
CheckOnlyCmd = ReactiveCommand.CreateFromTask(CheckOnly);
CheckOnlyCmd.ThrownExceptions.Subscribe(ex =>
{
Logging.SaveLog(_tag, ex);
_ = UpdateView(_v2rayN, ex.Message);
});
EnableCheckPreReleaseUpdate = _config.CheckUpdateItem.CheckPreReleaseUpdate;
this.WhenAnyValue(
x => x.EnableCheckPreReleaseUpdate,
y => y == true)
.Subscribe(c => _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate);
.Subscribe(c => _ = OnCheckPreReleaseUpdateChanged());
RefreshCheckUpdateItems();
}
@ -37,21 +45,15 @@ public class CheckUpdateViewModel : MyReactiveObject
{
CheckUpdateModels.Clear();
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
{
CheckUpdateModels.Add(GetCheckUpdateModel(_v2rayN));
//Not Windows and under Win10
if (!(Utils.IsWindows() && Environment.OSVersion.Version.Major < 10))
{
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.Xray.ToString()));
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.mihomo.ToString()));
CheckUpdateModels.Add(GetCheckUpdateModel(ECoreType.sing_box.ToString()));
}
CheckUpdateModels.Add(GetCheckUpdateModel(type));
}
CheckUpdateModels.Add(GetCheckUpdateModel(_geo));
CheckUpdateModels.Add(GetGeoFileCheckUpdateModel());
}
private CheckUpdateModel GetCheckUpdateModel(string coreType)
private CheckUpdateModel GetCheckUpdateModel(ECoreType coreType)
{
if (coreType == _v2rayN && Utils.IsPackagedInstall())
{
@ -59,34 +61,64 @@ public class CheckUpdateViewModel : MyReactiveObject
{
IsSelected = false,
CoreType = coreType,
Remarks = ResUI.menuCheckUpdate + " (Not Support)",
IsGeoFile = false,
Remarks = ResUI.menuCheckUpdate + $" ({ResUI.MsgNotSupport})",
};
}
AppManager.Instance.LastCheckUpdateResults.TryGetValue(coreType, out var lastResult);
return new()
{
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true,
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType.ToString()) ?? true,
CoreType = coreType,
IsGeoFile = false,
Remarks = lastResult ?? ResUI.menuCheckUpdate,
};
}
private CheckUpdateModel GetGeoFileCheckUpdateModel()
{
return new()
{
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(_geo) ?? true,
CoreType = null,
IsGeoFile = true,
Remarks = ResUI.menuCheckUpdate,
};
}
private async Task OnCheckPreReleaseUpdateChanged()
{
if (_config.CheckUpdateItem.CheckPreReleaseUpdate == EnableCheckPreReleaseUpdate)
{
return;
}
_config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate;
await SaveSelectedCoreTypes();
}
private async Task SaveSelectedCoreTypes()
{
_config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels.Where(t => t.IsSelected == true).Select(t => t.CoreType ?? "").ToList();
_config.CheckUpdateItem.SelectedCoreTypes =
CheckUpdateModels.Where(t => t.IsSelected == true)
.Select(t => t.CoreTypeForStorage)
.ToList();
await ConfigHandler.SaveConfig(_config);
}
private async Task CheckOnly()
{
await Task.Run(CheckOnlyTask);
}
private async Task CheckUpdate()
{
await Task.Run(CheckUpdateTask);
}
private async Task CheckUpdateTask()
private async Task CheckOnlyTask()
{
_lstUpdated.Clear();
_lstUpdated = CheckUpdateModels.Where(x => x.IsSelected == true)
.Select(x => new CheckUpdateModel() { CoreType = x.CoreType }).ToList();
await SaveSelectedCoreTypes();
for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
@ -98,7 +130,56 @@ public class CheckUpdateViewModel : MyReactiveObject
}
await UpdateView(item.CoreType, "...");
if (item.CoreType == _geo)
if (item.IsGeoFile || item.CoreType == null)
{
await UpdateView(item.CoreType, ResUI.menuCheckOnly + $" ({ResUI.MsgNotSupport})");
continue;
}
if (item.CoreType == null)
{
await UpdateView(item.CoreType, ResUI.MsgNotSupport);
continue;
}
var updateService = new UpdateService(_config, async (success, msg) => await Task.CompletedTask);
var result = await updateService.CheckHasUpdateOnly(item.CoreType.Value, EnableCheckPreReleaseUpdate);
if (result.Success && result.Version != null)
{
await UpdateView(item.CoreType, string.Format(ResUI.MsgCheckUpdateHasNewVersion, item.CoreType, result.Version));
}
else
{
await UpdateView(item.CoreType, result.Msg);
}
}
}
private async Task CheckUpdateTask()
{
_lstUpdated.Clear();
_lstUpdated = CheckUpdateModels
.Where(x => x.IsSelected == true)
.Select(x => new CheckUpdateModel()
{
CoreType = x.CoreType,
IsGeoFile = x.IsGeoFile
})
.ToList();
await SaveSelectedCoreTypes();
for (var k = CheckUpdateModels.Count - 1; k >= 0; k--)
{
var item = CheckUpdateModels[k];
if (item.IsSelected != true)
{
continue;
}
await UpdateView(item.CoreType, "...");
if (item.IsGeoFile)
{
await CheckUpdateGeo();
}
@ -106,16 +187,16 @@ public class CheckUpdateViewModel : MyReactiveObject
{
if (Utils.IsPackagedInstall())
{
await UpdateView(_v2rayN, "Not Support");
await UpdateView(_v2rayN, ResUI.MsgNotSupport);
continue;
}
await CheckUpdateN(EnableCheckPreReleaseUpdate);
}
else if (item.CoreType == ECoreType.Xray.ToString())
else if (item.CoreType == ECoreType.Xray)
{
await CheckUpdateCore(item, EnableCheckPreReleaseUpdate);
}
else
else if (item.CoreType.HasValue)
{
await CheckUpdateCore(item, false);
}
@ -124,7 +205,7 @@ public class CheckUpdateViewModel : MyReactiveObject
await UpdateFinished();
}
private void UpdatedPlusPlus(string coreType, string fileName)
private void UpdatedPlusPlus(ECoreType? coreType, string fileName)
{
var item = _lstUpdated.FirstOrDefault(x => x.CoreType == coreType);
if (item == null)
@ -142,14 +223,14 @@ public class CheckUpdateViewModel : MyReactiveObject
{
async Task _updateUI(bool success, string msg)
{
await UpdateView(_geo, msg);
await UpdateView(null, msg);
if (success)
{
UpdatedPlusPlus(_geo, "");
UpdatedPlusPlus(null, "");
}
}
await new UpdateService(_config, _updateUI).UpdateGeoFileAll()
.ContinueWith(t => UpdatedPlusPlus(_geo, ""));
.ContinueWith(t => UpdatedPlusPlus(null, ""));
}
private async Task CheckUpdateN(bool preRelease)
@ -175,13 +256,15 @@ public class CheckUpdateViewModel : MyReactiveObject
if (success)
{
await UpdateView(model.CoreType, ResUI.MsgUpdateV2rayCoreSuccessfullyMore);
UpdatedPlusPlus(model.CoreType, msg);
}
}
var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType);
await new UpdateService(_config, _updateUI).CheckUpdateCore(type, preRelease)
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
if (model.CoreType.HasValue)
{
await new UpdateService(_config, _updateUI).CheckUpdateCore(model.CoreType.Value, preRelease)
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
}
}
private async Task UpdateFinished()
@ -257,7 +340,7 @@ public class CheckUpdateViewModel : MyReactiveObject
{
foreach (var item in _lstUpdated)
{
if (item.FileName.IsNullOrEmpty())
if (item.FileName.IsNullOrEmpty() || item.IsGeoFile)
{
continue;
}
@ -267,7 +350,9 @@ public class CheckUpdateViewModel : MyReactiveObject
{
continue;
}
var toPath = Utils.GetBinPath("", item.CoreType);
var coreTypeStr = item.CoreType?.ToString() ?? "";
var toPath = Utils.GetBinPath("", coreTypeStr);
if (fileName.Contains(".tar.gz"))
{
@ -284,7 +369,7 @@ public class CheckUpdateViewModel : MyReactiveObject
}
else if (fileName.Contains(".gz"))
{
FileUtils.DecompressFile(fileName, toPath, item.CoreType);
FileUtils.DecompressFile(fileName, toPath, coreTypeStr);
}
else
{
@ -296,7 +381,7 @@ public class CheckUpdateViewModel : MyReactiveObject
var filesList = new DirectoryInfo(toPath).GetFiles().Select(u => u.FullName).ToList();
foreach (var file in filesList)
{
await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower()));
await Utils.SetLinuxChmod(Path.Combine(toPath, coreTypeStr.ToLower()));
}
}
@ -309,11 +394,12 @@ public class CheckUpdateViewModel : MyReactiveObject
}
}
private async Task UpdateView(string coreType, string msg)
private async Task UpdateView(ECoreType? coreType, string msg)
{
var item = new CheckUpdateModel()
{
CoreType = coreType,
IsGeoFile = coreType == null,
Remarks = msg,
};
@ -327,7 +413,7 @@ public class CheckUpdateViewModel : MyReactiveObject
public async Task UpdateViewResult(CheckUpdateModel model)
{
var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType);
var found = CheckUpdateModels.FirstOrDefault(t => t.CoreType == model.CoreType && t.IsGeoFile == model.IsGeoFile);
if (found == null)
{
return;

View file

@ -65,6 +65,8 @@ public class MainWindowViewModel : MyReactiveObject
[Reactive] public bool BlIsWindows { get; set; }
[Reactive] public bool BlNewUpdate { get; set; }
#endregion Menu
#region Init
@ -251,6 +253,11 @@ public class MainWindowViewModel : MyReactiveObject
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async bl => BlNewUpdate = bl);
#endregion AppEvents
_ = Init();
@ -297,10 +304,22 @@ public class MainWindowViewModel : MyReactiveObject
{
var indexIdOld = _config.IndexId;
await RefreshServers();
if (indexIdOld != _config.IndexId)
// If indexId changed or subIndexId is empty, directly reload.
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
{
await Reload();
}
else
{
// The activity config belongs to the current group.
var profile = await AppManager.Instance.GetProfileItem(_config.IndexId);
if (profile != null && profile.Subid == _config.SubIndexId)
{
await Reload();
}
}
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
{
AppEvents.AdjustMainLvColWidthRequested.Publish();

View file

@ -4,29 +4,29 @@ public class OptionSettingViewModel : MyReactiveObject
{
#region Core
[Reactive] public int localPort { get; set; }
[Reactive] public int LocalPort { get; set; }
[Reactive] public bool SecondLocalPortEnabled { get; set; }
[Reactive] public bool udpEnabled { get; set; }
[Reactive] public bool sniffingEnabled { get; set; }
public IList<string> destOverride { get; set; }
[Reactive] public bool routeOnly { get; set; }
[Reactive] public bool allowLANConn { get; set; }
[Reactive] public bool newPort4LAN { get; set; }
[Reactive] public string user { get; set; }
[Reactive] public string pass { get; set; }
[Reactive] public bool muxEnabled { get; set; }
[Reactive] public bool logEnabled { get; set; }
[Reactive] public string loglevel { get; set; }
[Reactive] public bool defAllowInsecure { get; set; }
[Reactive] public string defFingerprint { get; set; }
[Reactive] public string defUserAgent { get; set; }
[Reactive] public string sendThrough { get; set; }
[Reactive] public string bindInterface { get; set; }
[Reactive] public string mux4SboxProtocol { get; set; }
[Reactive] public bool enableCacheFile4Sbox { get; set; }
[Reactive] public int? hyUpMbps { get; set; }
[Reactive] public int? hyDownMbps { get; set; }
[Reactive] public bool enableFragment { get; set; }
[Reactive] public bool UdpEnabled { get; set; }
[Reactive] public bool SniffingEnabled { get; set; }
public IList<string> DestOverride { get; set; }
[Reactive] public bool RouteOnly { get; set; }
[Reactive] public bool AllowLANConn { get; set; }
[Reactive] public bool NewPort4LAN { get; set; }
[Reactive] public string User { get; set; }
[Reactive] public string Pass { get; set; }
[Reactive] public bool MuxEnabled { get; set; }
[Reactive] public bool LogEnabled { get; set; }
[Reactive] public string Loglevel { get; set; }
[Reactive] public bool DefAllowInsecure { get; set; }
[Reactive] public string DefFingerprint { get; set; }
[Reactive] public string DefUserAgent { get; set; }
[Reactive] public string SendThrough { get; set; }
[Reactive] public string BindInterface { get; set; }
[Reactive] public string Mux4SboxProtocol { get; set; }
[Reactive] public bool EnableCacheFile4Sbox { get; set; }
[Reactive] public int? HyUpMbps { get; set; }
[Reactive] public int? HyDownMbps { get; set; }
[Reactive] public bool EnableFragment { get; set; }
#endregion Core
@ -83,9 +83,9 @@ public class OptionSettingViewModel : MyReactiveObject
#region System proxy
[Reactive] public bool notProxyLocalAddress { get; set; }
[Reactive] public string systemProxyAdvancedProtocol { get; set; }
[Reactive] public string systemProxyExceptions { get; set; }
[Reactive] public bool NotProxyLocalAddress { get; set; }
[Reactive] public string SystemProxyAdvancedProtocol { get; set; }
[Reactive] public string SystemProxyExceptions { get; set; }
[Reactive] public string CustomSystemProxyPacPath { get; set; }
[Reactive] public string CustomSystemProxyScriptPath { get; set; }
@ -142,28 +142,28 @@ public class OptionSettingViewModel : MyReactiveObject
#region Core
var inbound = _config.Inbound.First();
localPort = inbound.LocalPort;
LocalPort = inbound.LocalPort;
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
udpEnabled = inbound.UdpEnabled;
sniffingEnabled = inbound.SniffingEnabled;
routeOnly = inbound.RouteOnly;
allowLANConn = inbound.AllowLANConn;
newPort4LAN = inbound.NewPort4LAN;
user = inbound.User;
pass = inbound.Pass;
muxEnabled = _config.CoreBasicItem.MuxEnabled;
logEnabled = _config.CoreBasicItem.LogEnabled;
loglevel = _config.CoreBasicItem.Loglevel;
defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure;
defFingerprint = _config.CoreBasicItem.DefFingerprint;
defUserAgent = _config.CoreBasicItem.DefUserAgent;
sendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty;
bindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty;
mux4SboxProtocol = _config.Mux4SboxItem.Protocol;
enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox;
hyUpMbps = _config.HysteriaItem.UpMbps;
hyDownMbps = _config.HysteriaItem.DownMbps;
enableFragment = _config.CoreBasicItem.EnableFragment;
UdpEnabled = inbound.UdpEnabled;
SniffingEnabled = inbound.SniffingEnabled;
RouteOnly = inbound.RouteOnly;
AllowLANConn = inbound.AllowLANConn;
NewPort4LAN = inbound.NewPort4LAN;
User = inbound.User;
Pass = inbound.Pass;
MuxEnabled = _config.CoreBasicItem.MuxEnabled;
LogEnabled = _config.CoreBasicItem.LogEnabled;
Loglevel = _config.CoreBasicItem.Loglevel;
DefAllowInsecure = _config.CoreBasicItem.DefAllowInsecure;
DefFingerprint = _config.CoreBasicItem.DefFingerprint;
DefUserAgent = _config.CoreBasicItem.DefUserAgent;
SendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty;
BindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty;
Mux4SboxProtocol = _config.Mux4SboxItem.Protocol;
EnableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox;
HyUpMbps = _config.HysteriaItem.UpMbps;
HyDownMbps = _config.HysteriaItem.DownMbps;
EnableFragment = _config.CoreBasicItem.EnableFragment;
#endregion Core
@ -211,9 +211,9 @@ public class OptionSettingViewModel : MyReactiveObject
#region System proxy
notProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress;
systemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol;
systemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions;
NotProxyLocalAddress = _config.SystemProxyItem.NotProxyLocalAddress;
SystemProxyAdvancedProtocol = _config.SystemProxyItem.SystemProxyAdvancedProtocol;
SystemProxyExceptions = _config.SystemProxyItem.SystemProxyExceptions;
CustomSystemProxyPacPath = _config.SystemProxyItem.CustomSystemProxyPacPath;
CustomSystemProxyScriptPath = _config.SystemProxyItem.CustomSystemProxyScriptPath;
@ -297,13 +297,13 @@ public class OptionSettingViewModel : MyReactiveObject
private async Task SaveSettingAsync()
{
if (localPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(localPort.ToString())
|| localPort <= 0 || localPort >= Global.MaxPort)
if (LocalPort.ToString().IsNullOrEmpty() || !Utils.IsNumeric(LocalPort.ToString())
|| LocalPort <= 0 || LocalPort >= Global.MaxPort)
{
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
return;
}
var sendThroughValue = sendThrough.TrimEx();
var sendThroughValue = SendThrough.TrimEx();
if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue))
{
NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4);
@ -328,33 +328,33 @@ public class OptionSettingViewModel : MyReactiveObject
//}
//Core
_config.Inbound.First().LocalPort = localPort;
_config.Inbound.First().LocalPort = LocalPort;
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
_config.Inbound.First().UdpEnabled = udpEnabled;
_config.Inbound.First().SniffingEnabled = sniffingEnabled;
_config.Inbound.First().DestOverride = destOverride?.ToList();
_config.Inbound.First().RouteOnly = routeOnly;
_config.Inbound.First().AllowLANConn = allowLANConn;
_config.Inbound.First().NewPort4LAN = newPort4LAN;
_config.Inbound.First().User = user;
_config.Inbound.First().Pass = pass;
_config.Inbound.First().UdpEnabled = UdpEnabled;
_config.Inbound.First().SniffingEnabled = SniffingEnabled;
_config.Inbound.First().DestOverride = DestOverride?.ToList();
_config.Inbound.First().RouteOnly = RouteOnly;
_config.Inbound.First().AllowLANConn = AllowLANConn;
_config.Inbound.First().NewPort4LAN = NewPort4LAN;
_config.Inbound.First().User = User;
_config.Inbound.First().Pass = Pass;
if (_config.Inbound.Count > 1)
{
_config.Inbound.RemoveAt(1);
}
_config.CoreBasicItem.LogEnabled = logEnabled;
_config.CoreBasicItem.Loglevel = loglevel;
_config.CoreBasicItem.MuxEnabled = muxEnabled;
_config.CoreBasicItem.DefAllowInsecure = defAllowInsecure;
_config.CoreBasicItem.DefFingerprint = defFingerprint;
_config.CoreBasicItem.DefUserAgent = defUserAgent;
_config.CoreBasicItem.SendThrough = sendThrough.TrimEx();
_config.CoreBasicItem.BindInterface = bindInterface.TrimEx();
_config.Mux4SboxItem.Protocol = mux4SboxProtocol;
_config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox;
_config.HysteriaItem.UpMbps = hyUpMbps ?? 0;
_config.HysteriaItem.DownMbps = hyDownMbps ?? 0;
_config.CoreBasicItem.EnableFragment = enableFragment;
_config.CoreBasicItem.LogEnabled = LogEnabled;
_config.CoreBasicItem.Loglevel = Loglevel;
_config.CoreBasicItem.MuxEnabled = MuxEnabled;
_config.CoreBasicItem.DefAllowInsecure = DefAllowInsecure;
_config.CoreBasicItem.DefFingerprint = DefFingerprint;
_config.CoreBasicItem.DefUserAgent = DefUserAgent;
_config.CoreBasicItem.SendThrough = SendThrough.TrimEx();
_config.CoreBasicItem.BindInterface = BindInterface.TrimEx();
_config.Mux4SboxItem.Protocol = Mux4SboxProtocol;
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
_config.CoreBasicItem.EnableFragment = EnableFragment;
_config.GuiItem.AutoRun = AutoRun;
_config.GuiItem.EnableStatistics = EnableStatistics;
@ -383,9 +383,9 @@ public class OptionSettingViewModel : MyReactiveObject
_config.SpeedTestItem.IPAPIUrl = IPAPIUrl;
//systemProxy
_config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions;
_config.SystemProxyItem.NotProxyLocalAddress = notProxyLocalAddress;
_config.SystemProxyItem.SystemProxyAdvancedProtocol = systemProxyAdvancedProtocol;
_config.SystemProxyItem.SystemProxyExceptions = SystemProxyExceptions;
_config.SystemProxyItem.NotProxyLocalAddress = NotProxyLocalAddress;
_config.SystemProxyItem.SystemProxyAdvancedProtocol = SystemProxyAdvancedProtocol;
_config.SystemProxyItem.CustomSystemProxyPacPath = CustomSystemProxyPacPath;
_config.SystemProxyItem.CustomSystemProxyScriptPath = CustomSystemProxyScriptPath;

View file

@ -303,6 +303,10 @@ public class ProfilesViewModel : MyReactiveObject
{
item.SpeedVal = result.Speed ?? string.Empty;
}
if (result.IpInfo.IsNotEmpty())
{
item.IpInfo = result.IpInfo ?? string.Empty;
}
await Task.CompletedTask;
}
@ -437,6 +441,7 @@ public class ProfilesViewModel : MyReactiveObject
Speed = t33?.Speed ?? 0,
DelayVal = t33?.Delay != 0 ? $"{t33?.Delay}" : string.Empty,
SpeedVal = t33?.Speed > 0 ? $"{t33?.Speed}" : t33?.Message ?? string.Empty,
IpInfo = t33?.IpInfo ?? string.Empty,
TodayDown = t22 == null ? "" : Utils.HumanFy(t22.TodayDown),
TodayUp = t22 == null ? "" : Utils.HumanFy(t22.TodayUp),
TotalDown = t22 == null ? "" : Utils.HumanFy(t22.TotalDown),

View file

@ -33,6 +33,12 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<Button
x:Name="btnCheckOnly"
Width="100"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.menuCheckOnly}" />
<Button
x:Name="btnCheckUpdate"
Width="100"
@ -70,7 +76,7 @@
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding CoreType}" />
Text="{Binding CoreTypeForStorage}" />
<TextBlock
Grid.Column="2"
HorizontalAlignment="Left"

View file

@ -13,6 +13,7 @@ public partial class CheckUpdateView : ReactiveUserControl<CheckUpdateViewModel>
this.OneWayBind(ViewModel, vm => vm.CheckUpdateModels, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCheckPreReleaseUpdate, v => v.togEnableCheckPreReleaseUpdate.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckOnlyCmd, v => v.btnCheckOnly).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
});
}

View file

@ -102,6 +102,13 @@
<MenuItem x:Name="menuClose" Header="{x:Static resx:ResUI.menuExit}" />
</Menu>
<Button
x:Name="btnNewUpdate"
Margin="{StaticResource MarginLr8}"
HorizontalAlignment="Left"
Content="{x:Static resx:ResUI.menuNewUpdate}"
IsVisible="False" />
</DockPanel>
<view:StatusBarView DockPanel.Dock="Bottom" />

View file

@ -25,6 +25,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
menuSettingsSetUWP.Click += MenuSettingsSetUWP_Click;
menuPromotion.Click += MenuPromotion_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click;
btnNewUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
menuClose.Click += MenuClose_Click;
@ -102,6 +103,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.IsVisible).DisposeWith(disposables);
switch (_config.UiItem.MainGirdOrientation)
{
@ -367,6 +369,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
{
_checkUpdateView ??= new CheckUpdateView();
DialogHost.Show(_checkUpdateView);
AppEvents.HasUpdateNotified.Publish(false);
}
private void MenuBackupAndRestore_Click(object? sender, RoutedEventArgs e)

View file

@ -876,7 +876,8 @@
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
HorizontalAlignment="Left"
IsEditable="True" />
<TextBlock
Grid.Row="6"

View file

@ -60,30 +60,30 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.localPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.LocalPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SecondLocalPortEnabled, v => v.togSecondLocalPortEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.udpEnabled, v => v.togudpEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.routeOnly, v => v.togrouteOnly.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.allowLANConn, v => v.togAllowLANConn.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtuser.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.user, v => v.txtuser.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.muxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.logEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.loglevel, v => v.cmbloglevel.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.bindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.UdpEnabled, v => v.togudpEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RouteOnly, v => v.togrouteOnly.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AllowLANConn, v => v.togAllowLANConn.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NewPort4LAN, v => v.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NewPort4LAN, v => v.txtuser.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NewPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.User, v => v.txtuser.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.LogEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Loglevel, v => v.cmbloglevel.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DefAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DefFingerprint, v => v.cmbdefFingerprint.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DefUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables);
@ -104,21 +104,21 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.SubConvertUrl, v => v.cmbSubConvertUrl.Text).DisposeWith(disposables);
this.Bind<OptionSettingViewModel, OptionSettingWindow, int, int>(ViewModel,
vm => vm.MainGirdOrientation, view => view.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NotProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SystemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SystemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyPacPath, v => v.txtCustomSystemProxyPacPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyScriptPath, v => v.txtCustomSystemProxyScriptPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables);
@ -161,29 +161,18 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
private async Task<List<string>> GetFonts()
{
await Task.CompletedTask;
var lstFonts = new List<string>();
try
{
if (Utils.IsWindows())
{
return lstFonts;
}
else if (Utils.IsNonWindows())
{
var result = await Utils.GetLinuxFontFamily("zh");
if (result.IsNullOrEmpty())
{
return lstFonts;
}
var lst = result.Split(Environment.NewLine)
.Where(t => t.IsNotEmpty())
.ToList()
.Select(t => t.Split(",").FirstOrDefault() ?? "")
.OrderBy(t => t)
.ToList();
return lst;
}
var lst = Avalonia.Media.FontManager.Current.SystemFonts
.Select(t => t.Name)
.Where(t => t.IsNotEmpty())
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(t => t)
.ToList();
return lst;
}
catch (Exception ex)
{
@ -196,7 +185,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
{
if (ViewModel != null)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
ViewModel.DestOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
}
}

View file

@ -277,6 +277,12 @@
Header="{x:Static resx:ResUI.LvTestSpeed}"
Tag="SpeedVal" />
<DataGridTextColumn
Width="100"
Binding="{Binding IpInfo}"
Header="{x:Static resx:ResUI.LvTestIpInfo}"
Tag="IpInfo" />
<DataGridTextColumn
Width="100"
Binding="{Binding TodayUp}"

View file

@ -425,10 +425,14 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
if (item.Name.StartsWith("to", StringComparison.CurrentCultureIgnoreCase))
{
item2.IsVisible = _config.GuiItem.EnableStatistics;
}
if (item.Name.Equals("IpInfo", StringComparison.CurrentCultureIgnoreCase))
{
item2.IsVisible = _config.SpeedTestItem.IPAPIUrl.IsNotEmpty();
}
}
}
}

View file

@ -16,7 +16,7 @@
<DockPanel>
<Grid
Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto,*"
DockPanel.Dock="Top"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock
@ -66,7 +66,8 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleTypeTips}" />
Text="{x:Static resx:ResUI.TbRuleTypeTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="2"
@ -93,7 +94,10 @@
Margin="0,0,8,0"
Click="BtnSelectProfile_Click"
Content="{x:Static resx:ResUI.TbSelectProfile}" />
<TextBlock VerticalAlignment="Center" Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
<TextBlock
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}"
TextWrapping="Wrap" />
</StackPanel>
<TextBlock
@ -115,7 +119,8 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleMatchingTips}" />
Text="{x:Static resx:ResUI.TbRuleMatchingTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
@ -135,7 +140,8 @@
Grid.Row="4"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center">
VerticalAlignment="Center"
TextWrapping="Wrap">
<HyperlinkButton Classes="WithIcon" Click="linkRuleobjectDoc_Click">
<TextBlock Text="{x:Static resx:ResUI.TbRuleobjectDoc}" />
</HyperlinkButton>
@ -160,7 +166,8 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRoutingInboundTagTips}" />
Text="{x:Static resx:ResUI.TbRoutingInboundTagTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="6"
@ -181,7 +188,8 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRoutingTips}" />
Text="{x:Static resx:ResUI.TbRoutingTips}"
TextWrapping="Wrap" />
</Grid>
<StackPanel

View file

@ -32,6 +32,13 @@
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<Button
x:Name="btnCheckOnly"
Width="100"
Margin="{StaticResource Margin8}"
Content="{x:Static resx:ResUI.menuCheckOnly}"
Style="{StaticResource DefButton}" />
<Button
x:Name="btnCheckUpdate"
Width="100"
@ -77,7 +84,7 @@
<TextBlock
Grid.Column="1"
Style="{StaticResource ListItemTitle}"
Text="{Binding CoreType}" />
Text="{Binding CoreTypeForStorage}" />
<TextBlock
Grid.Column="2"
Style="{StaticResource ListItemSubTitle}"

View file

@ -13,6 +13,7 @@ public partial class CheckUpdateView
this.OneWayBind(ViewModel, vm => vm.CheckUpdateModels, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCheckPreReleaseUpdate, v => v.togEnableCheckPreReleaseUpdate.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckOnlyCmd, v => v.btnCheckOnly).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
});
}

View file

@ -313,6 +313,13 @@
Style="{StaticResource MaterialDesignToolForegroundPopupBox}">
<ContentControl x:Name="pbTheme" />
</materialDesign:PopupBox>
<Button
x:Name="btnNewUpdate"
Margin="{StaticResource MarginLeftRight8}"
Content="{x:Static resx:ResUI.menuNewUpdate}"
Style="{StaticResource DefButton}"
Visibility="Hidden" />
</ToolBar>
</ToolBarTray>

View file

@ -25,6 +25,7 @@ public partial class MainWindow
menuPromotion.Click += MenuPromotion_Click;
menuClose.Click += MenuClose_Click;
menuCheckUpdate.Click += MenuCheckUpdate_Click;
btnNewUpdate.Click += MenuCheckUpdate_Click;
menuBackupAndRestore.Click += MenuBackupAndRestore_Click;
ViewModel = new MainWindowViewModel(UpdateViewHandler);
@ -102,6 +103,8 @@ public partial class MainWindow
this.BindCommand(ViewModel, vm => vm.ReloadCmd, v => v.menuReload).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlReloadEnabled, v => v.menuReload.IsEnabled).DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.BlNewUpdate, v => v.btnNewUpdate.Visibility).DisposeWith(disposables);
switch (_config.UiItem.MainGirdOrientation)
{
case EGirdOrientation.Horizontal:
@ -363,6 +366,8 @@ public partial class MainWindow
{
_checkUpdateView ??= new CheckUpdateView();
DialogHost.Show(_checkUpdateView, "RootDialog");
AppEvents.HasUpdateNotified.Publish(false);
}
private void MenuBackupAndRestore_Click(object sender, RoutedEventArgs e)

View file

@ -1141,6 +1141,7 @@
Width="200"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left"
IsEditable="True"
Style="{StaticResource DefComboBox}" />
<TextBlock

View file

@ -57,30 +57,30 @@ public partial class OptionSettingWindow
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm => vm.localPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.LocalPort, v => v.txtlocalPort.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SecondLocalPortEnabled, v => v.togSecondLocalPortEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.udpEnabled, v => v.togudpEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.routeOnly, v => v.togrouteOnly.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.allowLANConn, v => v.togAllowLANConn.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtuser.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.user, v => v.txtuser.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.muxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.logEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.loglevel, v => v.cmbloglevel.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.bindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.UdpEnabled, v => v.togudpEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RouteOnly, v => v.togrouteOnly.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AllowLANConn, v => v.togAllowLANConn.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NewPort4LAN, v => v.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NewPort4LAN, v => v.txtuser.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NewPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.User, v => v.txtuser.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.LogEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Loglevel, v => v.cmbloglevel.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DefAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DefFingerprint, v => v.cmbdefFingerprint.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DefUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Mux4SboxProtocol, v => v.cmbmux4SboxProtocol.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.Text).DisposeWith(disposables);
@ -114,9 +114,9 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.NotProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SystemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SystemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyPacPath, v => v.txtCustomSystemProxyPacPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
@ -217,7 +217,7 @@ public partial class OptionSettingWindow
{
if (ViewModel != null)
{
ViewModel.destOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
ViewModel.DestOverride = clbdestOverride.SelectedItems.Cast<string>().ToList();
}
}

View file

@ -345,6 +345,14 @@
Binding="{Binding TodayUp}"
ExName="TodayUp"
Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" />
<base:MyDGTextColumn
x:Name="colIpInfo"
Width="100"
Binding="{Binding IpInfo}"
ExName="IpInfo"
Header="{x:Static resx:ResUI.LvTestIpInfo}" />
<base:MyDGTextColumn
x:Name="colTodayDown"
Width="100"

View file

@ -378,10 +378,14 @@ public partial class ProfilesView
item2.Width = item.Width;
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
if (item.Name.StartsWith("to", StringComparison.CurrentCultureIgnoreCase))
{
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden;
}
if (item.Name.Equals("IpInfo", StringComparison.CurrentCultureIgnoreCase))
{
item2.Visibility = _config.SpeedTestItem.IPAPIUrl.IsNotEmpty() ? Visibility.Visible : Visibility.Hidden;
}
}
}
}