Compare commits

..

22 commits

Author SHA1 Message Date
JieXu
c7efe39e1c
Fix (#9418)
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 package-osx.sh

* Update package-osx.sh
2026-05-30 20:50:20 +08:00
2dust
a74ca3f9d0 up 7.22.4
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-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
52 changed files with 625 additions and 246 deletions

View file

@ -100,7 +100,6 @@ csharp_style_prefer_tuple_swap = true:warning
csharp_style_prefer_utf8_string_literals = true:warning csharp_style_prefer_utf8_string_literals = true:warning
csharp_style_throw_expression = true:warning csharp_style_throw_expression = true:warning
csharp_style_unused_value_assignment_preference = discard_variable: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_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_colon_in_constructor_initializer_experimental = true:warning
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_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: env:
RELEASE_TAG: ${{ case(inputs.release_tag != '', inputs.release_tag, github.ref_name) }} 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 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.4/edk2-loongarch64-code.fd 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.4/edk2-loongarch64-vars.fd EFI_VARS_URL: https://github.com/xujiegb/debian-loong64-qcow2/releases/download/13.5/edk2-loongarch64-vars.fd
QCOW2_IMAGE: debian13-loong64.qcow2 QCOW2_IMAGE: debian13-loong64.qcow2
EFI_CODE: edk2-loongarch64-code.fd EFI_CODE: edk2-loongarch64-code.fd
EFI_VARS: edk2-loongarch64-vars.fd EFI_VARS: edk2-loongarch64-vars.fd

View file

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

View file

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

View file

@ -22,7 +22,17 @@ cat >"$PackagePath/v2rayN.app/Contents/Info.plist" <<-EOF
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>en</string>
<key>CFBundleLocalizations</key>
<array>
<string>zh-Hans</string>
<string>zh-Hant</string>
<string>en</string>
<string>fa</string>
<string>fr</string>
<string>ru</string>
<string>hu</string>
</array>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>v2rayN</string> <string>v2rayN</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>

View file

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

View file

@ -7,8 +7,8 @@
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" /> <PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.16" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.16" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" /> <PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" /> <PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" /> <PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.12" />
@ -26,7 +26,7 @@
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" /> <PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
<PackageVersion Include="NLog" Version="6.1.3" /> <PackageVersion Include="NLog" Version="6.1.3" />
<PackageVersion Include="sqlite-net-e" Version="1.11.0" /> <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="TaskScheduler" Version="2.12.2" />
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" /> <PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" />
<PackageVersion Include="WebDav.Client" Version="2.9.0" /> <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, SubRemarks,
DelayVal, DelayVal,
SpeedVal, SpeedVal,
IpInfo,
TodayDown, TodayDown,
TodayUp, TodayUp,

View file

@ -7,6 +7,7 @@ public static class AppEvents
public static readonly EventChannel<Unit> AddServerViaScanRequested = new(); public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new(); public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = 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> ProfilesRefreshRequested = new();
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = 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 DnsOutboundTag = "dns";
public const string DnsTag = "dns-module"; public const string DnsTag = "dns-module";
public const string DirectDnsTag = "direct-dns"; public const string DirectDnsTag = "direct-dns";
public const string BalancerTagSuffix = "-round"; public const string BalancerTagSuffix = "-balancer";
public const string StreamSecurity = "tls"; public const string StreamSecurity = "tls";
public const string StreamSecurityReality = "reality"; public const string StreamSecurityReality = "reality";
public const string Loopback = "127.0.0.1"; public const string Loopback = "127.0.0.1";
@ -149,6 +149,9 @@ public class Global
public static readonly List<string> SpeedTestUrls = public static readonly List<string> SpeedTestUrls =
[ [
@"https://cachefly.cachefly.net/50mb.test", @"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=10000000",
@"https://speed.cloudflare.com/__down?bytes=50000000", @"https://speed.cloudflare.com/__down?bytes=50000000",
@"https://speed.cloudflare.com/__down?bytes=99999999", @"https://speed.cloudflare.com/__down?bytes=99999999",
@ -157,6 +160,8 @@ public class Global
public static readonly List<string> SpeedPingTestUrls = public static readonly List<string> SpeedPingTestUrls =
[ [
@"https://www.google.com/generate_204", @"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.gstatic.com/generate_204",
@"https://www.apple.com/library/test/success.html", @"https://www.apple.com/library/test/success.html",
@"http://www.msftconnecttest.com/connecttest.txt" @"http://www.msftconnecttest.com/connecttest.txt"
@ -207,6 +212,10 @@ public class Global
public const string NaiveQuicProtocolShare = "naive+quic://"; public const string NaiveQuicProtocolShare = "naive+quic://";
public const string SOCKS5Protocol = "socks5://";
public const string SOCKS4Protocol = "socks4://";
public static readonly Dictionary<EConfigType, string> ProtocolShares = new() public static readonly Dictionary<EConfigType, string> ProtocolShares = new()
{ {
{ EConfigType.VMess, "vmess://" }, { EConfigType.VMess, "vmess://" },

View file

@ -111,7 +111,8 @@ public static class AutoStartupHandler
task.Settings.RunOnlyIfIdle = false; task.Settings.RunOnlyIfIdle = false;
task.Settings.IdleSettings.StopOnIdleEnd = false; task.Settings.IdleSettings.StopOnIdleEnd = false;
task.Settings.ExecutionTimeLimit = TimeSpan.Zero; 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.Principal.RunLevel = Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;
task.Actions.Add(new Microsoft.Win32.TaskScheduler.ExecAction(fileName.AppendQuotes(), null, Path.GetDirectoryName(fileName))); 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, Delay = t33?.Delay ?? 0,
Speed = t33?.Speed ?? 0, Speed = t33?.Speed ?? 0,
Sort = t33?.Sort ?? 0, Sort = t33?.Sort ?? 0,
IpInfo = t33?.IpInfo ?? string.Empty,
TodayDown = (t22?.TodayDown ?? 0).ToString("D16"), TodayDown = (t22?.TodayDown ?? 0).ToString("D16"),
TodayUp = (t22?.TodayUp ?? 0).ToString("D16"), TodayUp = (t22?.TodayUp ?? 0).ToString("D16"),
TotalDown = (t22?.TotalDown ?? 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.StreamSecurity => lstProfile.OrderBy(t => t.StreamSecurity).ToList(),
EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(), EServerColName.DelayVal => lstProfile.OrderBy(t => t.Delay).ToList(),
EServerColName.SpeedVal => lstProfile.OrderBy(t => t.Speed).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.SubRemarks => lstProfile.OrderBy(t => t.Subid).ToList(),
EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(), EServerColName.TodayDown => lstProfile.OrderBy(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderBy(t => t.TodayUp).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.StreamSecurity => lstProfile.OrderByDescending(t => t.StreamSecurity).ToList(),
EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(), EServerColName.DelayVal => lstProfile.OrderByDescending(t => t.Delay).ToList(),
EServerColName.SpeedVal => lstProfile.OrderByDescending(t => t.Speed).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.SubRemarks => lstProfile.OrderByDescending(t => t.Subid).ToList(),
EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(), EServerColName.TodayDown => lstProfile.OrderByDescending(t => t.TodayDown).ToList(),
EServerColName.TodayUp => lstProfile.OrderByDescending(t => t.TodayUp).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"; 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() public static async Task<string> RunAvailabilityCheck()
{ {
var time = await GetRealPingTimeInfo(); 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); return string.Format(ResUI.TestMeOutput, time, ip);
} }
/// <summary>
/// Gets IP information using the default local proxy.
/// </summary>
private static async Task<string?> GetIPInfo() private static async Task<string?> GetIPInfo()
{ {
var url = AppManager.Instance.Config.SpeedTestItem.IPAPIUrl; var webProxy = await GetWebProxy();
if (url.IsNullOrEmpty())
{ var ipInfo = await GetIPInfo(webProxy);
return null; return ipInfo?.ToString() ?? Global.None;
}
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}";
} }
/// <summary>
/// Measures real ping time using configured test URL.
/// </summary>
private static async Task<int> GetRealPingTimeInfo() private static async Task<int> GetRealPingTimeInfo()
{ {
var responseTime = -1; var responseTime = -1;
try try
{ {
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); var webProxy = await GetWebProxy();
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{port}");
var url = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl;
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
{ {
responseTime = await GetRealPingTime(url, webProxy, 10); responseTime = await GetRealPingTime(webProxy, 10);
if (responseTime > 0) if (responseTime > 0)
{ {
break; break;
@ -66,8 +54,21 @@ public static class ConnectionHandler
return responseTime; 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; var responseTime = -1;
try try
{ {
@ -95,4 +96,41 @@ public static class ConnectionHandler
} }
return responseTime; 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); 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); return SocksFmt.Resolve(str, out msg);
} }
@ -65,7 +67,8 @@ public class FmtHandler
{ {
return VLESSFmt.Resolve(str, out msg); 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); return Hysteria2Fmt.Resolve(str, out msg);
} }

View file

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

View file

@ -150,6 +150,14 @@ public class ProfileExManager
IndexIdEnqueue(indexId); 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) public void SetSort(string indexId, int sort)
{ {
var profileEx = GetProfileExItem(indexId); var profileEx = GetProfileExItem(indexId);

View file

@ -142,5 +142,10 @@ public class TaskManager
await _updateFunc?.Invoke(false, msg); await _updateFunc?.Invoke(false, msg);
} }
NoticeManager.Instance.Enqueue(string.Join("\n", msgs)); 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 int MixedConcurrencyCount { get; set; }
public string IPAPIUrl { get; set; } public string IPAPIUrl { get; set; }
public string UdpTestTarget { get; set; } public string UdpTestTarget { get; set; }
public int? SpeedTestPageSize { get; set; }
public int? SpeedTestDelayInterval { get; set; }
} }
[Serializable] [Serializable]

View file

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

View file

@ -17,3 +17,12 @@ public class LocationInfo
{ {
public string? country_code { get; set; } 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] [Reactive]
public string SpeedVal { get; set; } public string SpeedVal { get; set; }
[Reactive]
public string IpInfo { get; set; }
[Reactive] [Reactive]
public string TodayUp { get; set; } public string TodayUp { get; set; }

View file

@ -8,4 +8,6 @@ public class SpeedTestResult
public string? Delay { get; set; } public string? Delay { get; set; }
public string? Speed { 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 decimal Speed { get; set; }
public int Sort { get; set; } public int Sort { get; set; }
public string? Message { 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> /// <summary>
/// 查找类似 Speed (MB/s) 的本地化字符串。 /// 查找类似 Speed (MB/s) 的本地化字符串。
/// </summary> /// </summary>
@ -1311,6 +1320,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 New Update 的本地化字符串。
/// </summary>
public static string menuNewUpdate {
get {
return ResourceManager.GetString("menuNewUpdate", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Open the storage location 的本地化字符串。 /// 查找类似 Open the storage location 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1737,4 +1737,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1740,4 +1740,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1740,4 +1740,10 @@
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>Not Support</value> <value>Not Support</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP Info</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>New Update</value>
</data>
</root> </root>

View file

@ -1737,4 +1737,10 @@
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>不支持</value> <value>不支持</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP 信息</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
</root> </root>

View file

@ -1737,4 +1737,10 @@
<data name="MsgNotSupport" xml:space="preserve"> <data name="MsgNotSupport" xml:space="preserve">
<value>不支援</value> <value>不支援</value>
</data> </data>
<data name="LvTestIpInfo" xml:space="preserve">
<value>IP 資訊</value>
</data>
<data name="menuNewUpdate" xml:space="preserve">
<value>有更新</value>
</data>
</root> </root>

View file

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

View file

@ -3,7 +3,7 @@ using System.Net.Http.Headers;
namespace ServiceLib.Services; namespace ServiceLib.Services;
/// <summary> /// <summary>
///Download /// Download
/// </summary> /// </summary>
public class DownloadService public class DownloadService
{ {
@ -13,7 +13,10 @@ public class DownloadService
private static readonly string _tag = "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 try
{ {
@ -36,6 +39,9 @@ public class DownloadService
return 0; return 0;
} }
/// <summary>
/// Downloads a file and reports progress through events.
/// </summary>
public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout) public async Task DownloadFileAsync(string url, string fileName, bool blProxy, int downloadTimeout)
{ {
try 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) public async Task<string?> UrlRedirectAsync(string url, bool blProxy)
{ {
var webRequestHandler = new SocketsHttpHandler 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) 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 try
{ {
var result1 = await DownloadStringAsync(url, blProxy, userAgent, 15); var result1 = await DownloadStringAsync(url, webProxy, userAgent, 15);
if (result1.IsNotEmpty()) if (result1.IsNotEmpty())
{ {
return result1; return result1;
@ -108,7 +129,7 @@ public class DownloadService
try try
{ {
var result2 = await DownloadStringViaDownloader(url, blProxy, userAgent, 15); var result2 = await DownloadStringViaDownloader(url, webProxy, userAgent, 15);
if (result2.IsNotEmpty()) if (result2.IsNotEmpty())
{ {
return result2; return result2;
@ -128,14 +149,12 @@ public class DownloadService
} }
/// <summary> /// <summary>
/// DownloadString /// Downloads string content via HttpClient.
/// </summary> /// </summary>
/// <param name="url"></param> private async Task<string?> DownloadStringAsync(string url, IWebProxy? webProxy, string userAgent, int timeout)
private async Task<string?> DownloadStringAsync(string url, bool blProxy, string userAgent, int timeout)
{ {
try try
{ {
var webProxy = await GetWebProxy(blProxy);
var client = new HttpClient(new SocketsHttpHandler() var client = new HttpClient(new SocketsHttpHandler()
{ {
Proxy = webProxy, Proxy = webProxy,
@ -172,15 +191,12 @@ public class DownloadService
} }
/// <summary> /// <summary>
/// DownloadString /// Downloads string content via DownloaderHelper.
/// </summary> /// </summary>
/// <param name="url"></param> private async Task<string?> DownloadStringViaDownloader(string url, IWebProxy? webProxy, string userAgent, int timeout)
private async Task<string?> DownloadStringViaDownloader(string url, bool blProxy, string userAgent, int timeout)
{ {
try try
{ {
var webProxy = await GetWebProxy(blProxy);
if (userAgent.IsNullOrEmpty()) if (userAgent.IsNullOrEmpty())
{ {
userAgent = Utils.GetVersion(false); userAgent = Utils.GetVersion(false);
@ -200,6 +216,9 @@ public class DownloadService
return null; return null;
} }
/// <summary>
/// Creates local SOCKS proxy when proxy switch is enabled.
/// </summary>
private async Task<WebProxy?> GetWebProxy(bool blProxy) private async Task<WebProxy?> GetWebProxy(bool blProxy)
{ {
if (!blProxy) if (!blProxy)
@ -215,6 +234,9 @@ public class DownloadService
return new WebProxy($"socks5://{Global.Loopback}:{port}"); 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) private async Task<bool> SocketCheck(string ip, int port)
{ {
try try

View file

@ -8,6 +8,8 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private readonly Config? _config = config; private readonly Config? _config = config;
private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc; private readonly Func<SpeedTestResult, Task>? _updateFunc = updateFunc;
private static readonly ConcurrentBag<string> _lstExitLoop = new(); 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) public void RunLoop(ESpeedActionType actionType, List<ProfileItem> selecteds)
{ {
@ -134,9 +136,14 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
} }
private async Task RunTcpingAsync(List<ServerTestItem> selecteds) private async Task RunTcpingAsync(List<ServerTestItem> selecteds)
{
var pageSize = Math.Min(selecteds.Count, _speedTestPageSize);
var lstBatch = GetTestBatchItem(selecteds, pageSize);
foreach (var lst in lstBatch)
{ {
List<Task> tasks = []; List<Task> tasks = [];
foreach (var it in selecteds) foreach (var it in lst)
{ {
tasks.Add(Task.Run(async () => tasks.Add(Task.Run(async () =>
{ {
@ -154,13 +161,15 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
})); }));
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
await Task.Delay(_delayInterval);
}
} }
private async Task RunRealPingBatchAsync(List<ServerTestItem> lstSelected, string exitLoopKey, int pageSize = 0) private async Task RunRealPingBatchAsync(List<ServerTestItem> lstSelected, string exitLoopKey, int pageSize = 0)
{ {
if (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); var lstTest = GetTestBatchItem(lstSelected, pageSize);
@ -172,7 +181,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
lstFailed.AddRange(lst); lstFailed.AddRange(lst);
} }
await Task.Delay(100); await Task.Delay(_delayInterval);
} }
//Retest the failed part //Retest the failed part
@ -249,7 +258,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
if (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); var lstTest = GetTestBatchItem(lstSelected, pageSize);
@ -261,7 +270,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
{ {
lstFailed.AddRange(lst); lstFailed.AddRange(lst);
} }
await Task.Delay(100); await Task.Delay(_delayInterval);
} }
//Retest the failed part //Retest the failed part
@ -392,10 +401,23 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
private async Task<int> DoRealPing(ServerTestItem it) private async Task<int> DoRealPing(ServerTestItem it)
{ {
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{it.Port}"); 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); ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
await UpdateFunc(it.IndexId, responseTime.ToString()); 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; return responseTime;
} }
@ -491,4 +513,9 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
ProfileExManager.Instance.SetTestMessage(indexId, speed); ProfileExManager.Instance.SetTestMessage(indexId, speed);
} }
} }
private async Task UpdateIpInfoFunc(string indexId, string ip)
{
await _updateFunc?.Invoke(new() { IndexId = indexId, IpInfo = ip });
}
} }

View file

@ -117,6 +117,11 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
var msgs = new List<string>(); var msgs = new List<string>();
foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes()) foreach (var type in CoreInfoManager.Instance.GetCheckUpdateCoreTypes())
{ {
if (!(_config.CheckUpdateItem.SelectedCoreTypes?.Contains(type.ToString()) ?? true))
{
continue;
}
var result = await CheckHasUpdateOnly(type, preRelease); var result = await CheckHasUpdateOnly(type, preRelease);
if (result.Success && result.Version != null) if (result.Success && result.Version != null)
{ {

View file

@ -36,7 +36,7 @@ public class CheckUpdateViewModel : MyReactiveObject
this.WhenAnyValue( this.WhenAnyValue(
x => x.EnableCheckPreReleaseUpdate, x => x.EnableCheckPreReleaseUpdate,
y => y == true) y => y == true)
.Subscribe(c => _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate); .Subscribe(c => _ = OnCheckPreReleaseUpdateChanged());
RefreshCheckUpdateItems(); RefreshCheckUpdateItems();
} }
@ -87,12 +87,23 @@ public class CheckUpdateViewModel : MyReactiveObject
}; };
} }
private async Task OnCheckPreReleaseUpdateChanged()
{
if (_config.CheckUpdateItem.CheckPreReleaseUpdate == EnableCheckPreReleaseUpdate)
{
return;
}
_config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate;
await SaveSelectedCoreTypes();
}
private async Task SaveSelectedCoreTypes() private async Task SaveSelectedCoreTypes()
{ {
_config.CheckUpdateItem.SelectedCoreTypes = CheckUpdateModels _config.CheckUpdateItem.SelectedCoreTypes =
.Where(t => t.IsSelected == true) CheckUpdateModels.Where(t => t.IsSelected == true)
.Select(t => t.CoreTypeForStorage) .Select(t => t.CoreTypeForStorage)
.ToList(); .ToList();
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
} }

View file

@ -65,6 +65,8 @@ public class MainWindowViewModel : MyReactiveObject
[Reactive] public bool BlIsWindows { get; set; } [Reactive] public bool BlIsWindows { get; set; }
[Reactive] public bool BlNewUpdate { get; set; }
#endregion Menu #endregion Menu
#region Init #region Init
@ -251,6 +253,11 @@ public class MainWindowViewModel : MyReactiveObject
.ObserveOn(RxSchedulers.MainThreadScheduler) .ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy)); .Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
AppEvents.HasUpdateNotified
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(async bl => BlNewUpdate = bl);
#endregion AppEvents #endregion AppEvents
_ = Init(); _ = Init();
@ -297,10 +304,22 @@ public class MainWindowViewModel : MyReactiveObject
{ {
var indexIdOld = _config.IndexId; var indexIdOld = _config.IndexId;
await RefreshServers(); await RefreshServers();
if (indexIdOld != _config.IndexId)
// If indexId changed or subIndexId is empty, directly reload.
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
{ {
await Reload(); 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) if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
{ {
AppEvents.AdjustMainLvColWidthRequested.Publish(); AppEvents.AdjustMainLvColWidthRequested.Publish();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -60,30 +60,30 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.WhenActivated(disposables => 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.SecondLocalPortEnabled, v => v.togSecondLocalPortEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.udpEnabled, v => v.togudpEnabled.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.SniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.routeOnly, v => v.togrouteOnly.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.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.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtuser.IsEnabled).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.NewPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.user, v => v.txtuser.Text).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.Pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.muxEnabled, v => v.togmuxEnabled.IsChecked).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.LogEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.loglevel, v => v.cmbloglevel.SelectedValue).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.DefAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.SelectedValue).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.DefUserAgent, v => v.cmbdefUserAgent.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.bindInterface, v => v.txtbindInterface.Text).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.SendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.SelectedValue).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.EnableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).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.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).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.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.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(ViewModel, vm => vm.SubConvertUrl, v => v.cmbSubConvertUrl.Text).DisposeWith(disposables);
this.Bind<OptionSettingViewModel, OptionSettingWindow, int, int>(ViewModel, this.Bind<OptionSettingViewModel, OptionSettingWindow, int, int>(ViewModel,
vm => vm.MainGirdOrientation, view => view.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables); 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.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).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.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).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.SystemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.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.CustomSystemProxyPacPath, v => v.txtCustomSystemProxyPacPath.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyScriptPath, v => v.txtCustomSystemProxyScriptPath.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.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.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.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.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.SelectedValue).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); this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables);
@ -161,30 +161,19 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
private async Task<List<string>> GetFonts() private async Task<List<string>> GetFonts()
{ {
await Task.CompletedTask;
var lstFonts = new List<string>(); var lstFonts = new List<string>();
try try
{ {
if (Utils.IsWindows()) var lst = Avalonia.Media.FontManager.Current.SystemFonts
{ .Select(t => t.Name)
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()) .Where(t => t.IsNotEmpty())
.ToList() .Distinct(StringComparer.OrdinalIgnoreCase)
.Select(t => t.Split(",").FirstOrDefault() ?? "")
.OrderBy(t => t) .OrderBy(t => t)
.ToList(); .ToList();
return lst; return lst;
} }
}
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog("GetFonts", ex); Logging.SaveLog("GetFonts", ex);
@ -196,7 +185,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
{ {
if (ViewModel != null) 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}" Header="{x:Static resx:ResUI.LvTestSpeed}"
Tag="SpeedVal" /> Tag="SpeedVal" />
<DataGridTextColumn
Width="100"
Binding="{Binding IpInfo}"
Header="{x:Static resx:ResUI.LvTestIpInfo}"
Tag="IpInfo" />
<DataGridTextColumn <DataGridTextColumn
Width="100" Width="100"
Binding="{Binding TodayUp}" Binding="{Binding TodayUp}"

View file

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

View file

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

View file

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

View file

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

View file

@ -57,30 +57,30 @@ public partial class OptionSettingWindow
this.WhenActivated(disposables => 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.SecondLocalPortEnabled, v => v.togSecondLocalPortEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.udpEnabled, v => v.togudpEnabled.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.SniffingEnabled, v => v.togsniffingEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.routeOnly, v => v.togrouteOnly.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.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.togNewPort4LAN.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.newPort4LAN, v => v.txtuser.IsEnabled).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.NewPort4LAN, v => v.txtpass.IsEnabled).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.user, v => v.txtuser.Text).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.Pass, v => v.txtpass.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.muxEnabled, v => v.togmuxEnabled.IsChecked).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.LogEnabled, v => v.toglogEnabled.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.loglevel, v => v.cmbloglevel.Text).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.DefAllowInsecure, v => v.togdefAllowInsecure.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.Text).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.DefUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.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.BindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.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.EnableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).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.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.enableFragment, v => v.togenableFragment.IsChecked).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.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.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.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.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.NotProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).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.SystemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CustomSystemProxyPacPath, v => v.txtCustomSystemProxyPacPath.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); this.Bind(ViewModel, vm => vm.TunAutoRoute, v => v.togAutoRoute.IsChecked).DisposeWith(disposables);
@ -217,7 +217,7 @@ public partial class OptionSettingWindow
{ {
if (ViewModel != null) 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}" Binding="{Binding TodayUp}"
ExName="TodayUp" ExName="TodayUp"
Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" /> 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 <base:MyDGTextColumn
x:Name="colTodayDown" x:Name="colTodayDown"
Width="100" Width="100"

View file

@ -378,10 +378,14 @@ public partial class ProfilesView
item2.Width = item.Width; item2.Width = item.Width;
item2.DisplayIndex = displayIndex++; 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; 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;
}
} }
} }
} }