mirror of
https://github.com/2dust/v2rayN.git
synced 2026-07-27 02:12:05 +03:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf8dd45abe | ||
|
|
9bba6f53f8 | ||
|
|
cd77f1d882 | ||
|
|
0427037638 | ||
|
|
e749b81ecf | ||
|
|
fd2c942231 | ||
|
|
fa42eb670f | ||
|
|
0d42996573 | ||
|
|
a37772f12b | ||
|
|
531e0e9443 | ||
|
|
984c77f0c4 | ||
|
|
b5e0be47a5 | ||
|
|
6df0e6de1b | ||
|
|
2b5328665f | ||
|
|
4584ecc5c9 | ||
|
|
d9dc2d7cf5 | ||
|
|
36f8565b7a | ||
|
|
f55d8b2565 | ||
|
|
09ea4890a7 | ||
|
|
5cc2aaba13 | ||
|
|
b8889bad86 | ||
|
|
5dd5b25869 | ||
|
|
fafdb4a0b6 | ||
|
|
223642dd67 | ||
|
|
165e0bf9e9 | ||
|
|
e615314582 | ||
|
|
ca9978b625 | ||
|
|
74ab7ad097 | ||
|
|
8f5cbad988 | ||
|
|
1ab9757498 | ||
|
|
50ab02e8a9 | ||
|
|
b9a1c129c4 | ||
|
|
996eb51f9b | ||
|
|
b9a58e7137 | ||
|
|
9664ee380e | ||
|
|
6f50206606 | ||
|
|
1de83f96ed | ||
|
|
994bc1ca6a | ||
|
|
3ca49dd9db | ||
|
|
a0bd8f9934 | ||
|
|
88d59488fd | ||
|
|
2c70b018ce | ||
|
|
b6ab428dfc | ||
|
|
45ab7503e3 | ||
|
|
1768b4a7ee |
107
.github/scripts/UpdateCert.cs
vendored
107
.github/scripts/UpdateCert.cs
vendored
@@ -1,3 +1,49 @@
|
||||
/*
|
||||
==============================================================================
|
||||
THIRD-PARTY CA CERTIFICATE DATA ATTRIBUTION
|
||||
==============================================================================
|
||||
|
||||
This component includes CA certificate data obtained from third-party sources.
|
||||
|
||||
1. Common CA Database (CCADB)
|
||||
------------------------------------------------------------------------------
|
||||
Website:
|
||||
https://www.ccadb.org/
|
||||
|
||||
Data source:
|
||||
https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites
|
||||
|
||||
License:
|
||||
Community Data License Agreement – Permissive, Version 2.0 (CDLA-2.0 Permissive)
|
||||
|
||||
The names and trademarks of CCADB contributors may not be used to endorse or
|
||||
promote products derived from this data without prior written permission.
|
||||
|
||||
License text:
|
||||
https://cdla.dev/permissive-2-0/
|
||||
|
||||
|
||||
2. Chromium Root Store
|
||||
------------------------------------------------------------------------------
|
||||
Source:
|
||||
Chromium Project
|
||||
|
||||
Data source:
|
||||
https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT
|
||||
|
||||
License:
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright:
|
||||
Copyright (c) The Chromium Authors
|
||||
|
||||
The Chromium Root Store data is distributed under the BSD 3-Clause License.
|
||||
|
||||
License text:
|
||||
https://opensource.org/licenses/BSD-3-Clause
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -27,68 +73,23 @@ Console.WriteLine("\nAll done!");
|
||||
|
||||
async Task ProcessMozillaAsync(string outputDir)
|
||||
{
|
||||
const string certdataUrl = "https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt";
|
||||
const string certdataUrl = "https://ccadb.my.salesforce-sites.com/mozilla/IncludedRootsPEMTxt?TrustBitsInclude=Websites";
|
||||
var outputFile = Path.Combine(outputDir, "mozilla_roots_pem");
|
||||
|
||||
Console.WriteLine("Downloading Mozilla certdata.txt...");
|
||||
var content = await client.GetStringAsync(certdataUrl);
|
||||
|
||||
Console.WriteLine("Parsing MULTILINE_OCTAL to PEM...");
|
||||
var pems = ParseMozillaCertData(content);
|
||||
var pemMatches = Regex.Matches(content, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||
var pems = new List<string>();
|
||||
foreach (Match m in pemMatches)
|
||||
{
|
||||
pems.Add(m.Groups[1].Value.Trim());
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(outputFile, string.Join("\n\n", pems));
|
||||
Console.WriteLine($"Mozilla roots saved to: {outputFile} ({pems.Count} certificates)");
|
||||
}
|
||||
|
||||
List<string> ParseMozillaCertData(string text)
|
||||
{
|
||||
var pems = new List<string>();
|
||||
|
||||
var matches = Regex.Matches(text,
|
||||
@"CKA_VALUE MULTILINE_OCTAL\s*([\s\S]*?)\s*END",
|
||||
RegexOptions.Multiline);
|
||||
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
var octalBlock = m.Groups[1].Value.Trim();
|
||||
var der = OctalToBytes(octalBlock);
|
||||
if (der.Length > 0)
|
||||
{
|
||||
var pem = ConvertToPem(der);
|
||||
pems.Add(pem);
|
||||
}
|
||||
}
|
||||
return pems;
|
||||
}
|
||||
|
||||
byte[] OctalToBytes(string octalBlock)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
var matches = Regex.Matches(octalBlock, @"\\(\d{1,3})");
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
bytes.Add((byte)Convert.ToInt32(m.Groups[1].Value, 8));
|
||||
}
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
string ConvertToPem(byte[] der)
|
||||
{
|
||||
var base64 = Convert.ToBase64String(der);
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("-----BEGIN CERTIFICATE-----\n");
|
||||
|
||||
for (int i = 0; i < base64.Length; i += 64)
|
||||
{
|
||||
var length = Math.Min(64, base64.Length - i);
|
||||
sb.Append(base64.Substring(i, length));
|
||||
sb.Append("\n");
|
||||
}
|
||||
|
||||
sb.Append("-----END CERTIFICATE-----\n");
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
async Task ProcessChromeAsync(string outputDir)
|
||||
{
|
||||
const string chromeCertsUrl = "https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/root_store.certs?format=TEXT";
|
||||
@@ -99,7 +100,7 @@ async Task ProcessChromeAsync(string outputDir)
|
||||
var decoded = Convert.FromBase64String(base64Content.Replace("\n", ""));
|
||||
|
||||
var text = Encoding.UTF8.GetString(decoded);
|
||||
|
||||
|
||||
var pemMatches = Regex.Matches(text, @"(-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----)");
|
||||
var pems = new List<string>();
|
||||
foreach (Match m in pemMatches)
|
||||
|
||||
2
.github/workflows/build-windows-x86.yml
vendored
2
.github/workflows/build-windows-x86.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET 10.0.1xx
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: 10.0.1xx
|
||||
|
||||
|
||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
dotnet --list-sdks 2>$null; $LASTEXITCODE=0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: '10.0.1xx'
|
||||
|
||||
@@ -83,6 +83,11 @@ jobs:
|
||||
working-directory: ./v2rayN
|
||||
run: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r $RID -p:SelfContained=true -p:PublishTrimmed=true $ExtOpt -o $Output
|
||||
|
||||
- name: Remove Debug Symbols
|
||||
shell: bash
|
||||
run: |
|
||||
find ${{ matrix.arch }} -type f -name '*.pdb' -delete
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v7.0.1
|
||||
with:
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v5.4.0
|
||||
uses: actions/setup-dotnet@v6.0.0
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
|
||||
9
.github/workflows/winget-publish.yml
vendored
9
.github/workflows/winget-publish.yml
vendored
@@ -23,8 +23,13 @@ jobs:
|
||||
|
||||
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1
|
||||
|
||||
$x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url
|
||||
$arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url
|
||||
$assets = $targetRelease | Select -ExpandProperty assets -First 1
|
||||
$x64InstallerUrl = $assets | Where-Object { $_.name -eq 'v2rayN-windows-64.zip' } | Select -ExpandProperty browser_download_url
|
||||
$arm64InstallerUrl = $assets | Where-Object { $_.name -eq 'v2rayN-windows-arm64.zip' } | Select -ExpandProperty browser_download_url
|
||||
|
||||
if (-not $x64InstallerUrl -or -not $arm64InstallerUrl) {
|
||||
throw "Could not find required installers in release assets."
|
||||
}
|
||||
|
||||
$ver = $targetRelease.tag_name
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
OUTPUT_DIR="${HOME}/debbuild"
|
||||
DOTNET_TFM="net10.0"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||
@@ -597,8 +597,8 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
|
||||
|
||||
mkdir -p "$workdir/debian"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
Source: v2rayn
|
||||
|
||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
OUTPUT_DIR="${HOME}/debbuild"
|
||||
DOTNET_RISCV_VERSION="10.0.109"
|
||||
DOTNET_RISCV_VERSION="10.0.110"
|
||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||
@@ -595,8 +595,8 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
|
||||
|
||||
mkdir -p "$workdir/debian"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
Source: v2rayn
|
||||
|
||||
@@ -609,7 +609,7 @@ package_binary() {
|
||||
write_desktop_file "$stage"
|
||||
write_maintainer_scripts "$debian_dir"
|
||||
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.2.0), coreutils (>= 9.5), bash (>= 5.2.26), libfreetype6 (>= 2.13)"
|
||||
extra_depends="libc6 (>= 2.39), fontconfig (>= 2.15.0), desktop-file-utils (>= 0.26), xdg-utils (>= 1.1.3), coreutils (>= 9.4), bash (>= 5.2.21), libfreetype6 (>= 2.13)"
|
||||
|
||||
mkdir -p "$workdir/debian"
|
||||
cat > "$workdir/debian/control" <<EOF
|
||||
|
||||
@@ -12,8 +12,8 @@ MIN_KERNEL="6.12"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.109"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.109-loongarch64"
|
||||
DOTNET_LOONGARCH_VERSION="10.0.110"
|
||||
DOTNET_LOONGARCH_TAG="v10.0.110-loongarch64"
|
||||
DOTNET_LOONGARCH_BASE="https://github.com/loongson/dotnet/releases/download"
|
||||
DOTNET_LOONGARCH_FILE="dotnet-sdk-${DOTNET_LOONGARCH_VERSION}-linux-loongarch64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_LOONGARCH_BASE}/${DOTNET_LOONGARCH_TAG}/${DOTNET_LOONGARCH_FILE}"
|
||||
@@ -513,9 +513,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.2.0
|
||||
Requires: coreutils >= 9.5
|
||||
Requires: bash >= 5.2.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
|
||||
@@ -12,7 +12,7 @@ MIN_KERNEL="5.10"
|
||||
PKGROOT="v2rayN-publish"
|
||||
PROJECT_HINT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||
RPM_TOPDIR="${HOME}/rpmbuild"
|
||||
DOTNET_RISCV_VERSION="10.0.109"
|
||||
DOTNET_RISCV_VERSION="10.0.110"
|
||||
DOTNET_RISCV_BASE="https://github.com/xujiegb/dotnet-riscv/releases/download"
|
||||
DOTNET_RISCV_FILE="dotnet-sdk-${DOTNET_RISCV_VERSION}-linux-riscv64.tar.gz"
|
||||
DOTNET_SDK_URL="${DOTNET_RISCV_BASE}/${DOTNET_RISCV_VERSION}/${DOTNET_RISCV_FILE}"
|
||||
@@ -512,9 +512,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.2.0
|
||||
Requires: coreutils >= 9.5
|
||||
Requires: bash >= 5.2.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
|
||||
@@ -501,9 +501,9 @@ Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
|
||||
Requires: glibc >= 2.39
|
||||
Requires: fontconfig >= 2.15.0
|
||||
Requires: desktop-file-utils >= 0.26
|
||||
Requires: xdg-utils >= 1.2.0
|
||||
Requires: coreutils >= 9.5
|
||||
Requires: bash >= 5.2.26
|
||||
Requires: xdg-utils >= 1.1.3
|
||||
Requires: coreutils >= 9.4
|
||||
Requires: bash >= 5.2.21
|
||||
Requires: freetype >= 2.13
|
||||
|
||||
%description
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>7.23.3</Version>
|
||||
<Version>7.24.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -5,35 +5,35 @@
|
||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.4.1" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.18" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.11.0" />
|
||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.0" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.0" />
|
||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||
<PackageVersion Include="AwesomeAssertions" Version="9.5.0" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.3" />
|
||||
<PackageVersion Include="IPNetwork2" Version="4.3.0" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.4.13" />
|
||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="12.0.3" />
|
||||
<PackageVersion Include="CliWrap" Version="3.10.2" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||
<PackageVersion Include="Downloader" Version="5.9.5" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.2" />
|
||||
<PackageVersion Include="QRCoder" Version="1.8.0" />
|
||||
<PackageVersion Include="ReactiveUI" Version="23.2.28" />
|
||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||
<PackageVersion Include="ReactiveUI.WPF" Version="23.2.28" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.14" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.2" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.3" />
|
||||
<PackageVersion Include="NLog" Version="6.1.3" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.0" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3" />
|
||||
<PackageVersion Include="Semi.Avalonia" Version="12.1.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="12.1.0" />
|
||||
<PackageVersion Include="NLog" Version="6.1.4" />
|
||||
<PackageVersion Include="sqlite-net-e" Version="1.11.285" />
|
||||
<PackageVersion Include="Repobot.SQLite.Unofficial" Version="3.53.3.10" />
|
||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.0.0" />
|
||||
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||
<PackageVersion Include="ZXing.Net.Bindings.SkiaSharp" Version="0.16.22" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -59,7 +59,7 @@ internal static class CoreConfigTestFactory
|
||||
},
|
||||
WebDavItem = new WebDavItem(),
|
||||
CheckUpdateItem = new CheckUpdateItem(),
|
||||
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Length = "100-200", Interval = "10-20" },
|
||||
Fragment4RayItem = new Fragment4RayItem { Packets = "tlshello", Lengths = ["100-200"], Delays = ["10-20"] },
|
||||
Inbound =
|
||||
[
|
||||
new InItem
|
||||
@@ -84,6 +84,7 @@ internal static class CoreConfigTestFactory
|
||||
ParallelQuery = false,
|
||||
Strategy4Freedom = Global.AsIs,
|
||||
Strategy4Proxy = Global.AsIs,
|
||||
Strategy4ProxyDial = Global.AsIs,
|
||||
},
|
||||
IndexId = string.Empty,
|
||||
SubIndexId = string.Empty,
|
||||
@@ -130,6 +131,25 @@ internal static class CoreConfigTestFactory
|
||||
};
|
||||
}
|
||||
|
||||
public static ProfileItem CreateHttpNode(ECoreType coreType, string indexId = "node-http-1",
|
||||
string remarks = "demo-http")
|
||||
{
|
||||
return new ProfileItem
|
||||
{
|
||||
IndexId = indexId,
|
||||
ConfigType = EConfigType.HTTP,
|
||||
CoreType = coreType,
|
||||
Remarks = remarks,
|
||||
Address = "proxy.example.com",
|
||||
Port = 8080,
|
||||
Password = "pass",
|
||||
Username = "user",
|
||||
Network = nameof(ETransport.raw),
|
||||
StreamSecurity = string.Empty,
|
||||
Subid = string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public static ProfileItem CreatePolicyGroupNode(ECoreType coreType, string indexId, string remarks,
|
||||
IEnumerable<string> childIndexIds)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,39 @@ public class CoreConfigV2rayServiceTests
|
||||
v2rayConfig.inbounds.Should().Contain(i => i.protocol == nameof(EInboundProtocol.mixed));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_HttpOutbound_ShouldEmitHeadersInSettings()
|
||||
{
|
||||
var config = CoreConfigTestFactory.CreateConfig(ECoreType.Xray);
|
||||
CoreConfigTestFactory.BindAppManagerConfig(config);
|
||||
var node = CoreConfigTestFactory.CreateHttpNode(ECoreType.Xray);
|
||||
node.SetProtocolExtra(node.GetProtocolExtra() with
|
||||
{
|
||||
HttpHeaders = "{\"User-Agent\":\"v2rayN\",\"Set-Cookie\":[\"a=1\",\"b=2\"]}",
|
||||
});
|
||||
var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.Xray);
|
||||
|
||||
var result = new CoreConfigV2rayService(context).GenerateClientConfigContent();
|
||||
|
||||
result.Success.Should().BeTrue();
|
||||
var cfg = JsonUtils.Deserialize<V2rayConfig>(result.Data!.ToString())!;
|
||||
var outbound = cfg.outbounds.First(o => o.tag == Global.ProxyTag && o.protocol == "http");
|
||||
|
||||
outbound.settings.address?.ToString().Should().Be("proxy.example.com");
|
||||
outbound.settings.port.Should().Be(8080);
|
||||
outbound.settings.user.Should().Be("user");
|
||||
outbound.settings.pass.Should().Be("pass");
|
||||
outbound.settings.level.Should().Be(1);
|
||||
outbound.settings.headers.Should().NotBeNull();
|
||||
var headers = JsonUtils.ParseJson(outbound.settings.headers.ToString());
|
||||
headers["User-Agent"]!.GetValue<string>().Should().Be("v2rayN");
|
||||
headers["Set-Cookie"]!.AsArray()
|
||||
.Select(item => item!.GetValue<string>())
|
||||
.Should().Equal("a=1", "b=2");
|
||||
outbound.settings.servers.Should().BeNull();
|
||||
outbound.settings.vnext.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateClientConfigContent_PolicyGroup_ShouldExpandChildrenAndBuildBalancer()
|
||||
{
|
||||
@@ -534,7 +567,7 @@ public class CoreConfigV2rayServiceTests
|
||||
|
||||
var directOutbound = cfg.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag && o.protocol == "freedom");
|
||||
directOutbound.Should().NotBeNull();
|
||||
directOutbound!.settings.domainStrategy.Should().Be("UseIPv4");
|
||||
directOutbound!.streamSettings.sockopt!.domainStrategy.Should().Be("UseIPv4");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
6
v2rayN/ServiceLib/Base/ICloseable.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public interface ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
}
|
||||
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
7
v2rayN/ServiceLib/Base/IWindowDialog.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public interface IWindowDialog
|
||||
{
|
||||
public Task<bool> ShowDialogAsync<TViewModel>(TViewModel vm)
|
||||
where TViewModel : class;
|
||||
}
|
||||
@@ -3,5 +3,4 @@ namespace ServiceLib.Base;
|
||||
public class MyReactiveObject : ReactiveObject
|
||||
{
|
||||
protected static Config? _config;
|
||||
protected Func<EViewAction, object?, Task<bool>>? _updateView;
|
||||
}
|
||||
|
||||
@@ -485,12 +485,17 @@ public class Utils
|
||||
|
||||
public static string? DomainStrategy4Sbox(string? strategy)
|
||||
{
|
||||
if (strategy is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return strategy switch
|
||||
{
|
||||
not null when strategy.StartsWith("UseIPv4") => "prefer_ipv4",
|
||||
not null when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||
not null when strategy.StartsWith("ForceIPv4") => "ipv4_only",
|
||||
not null when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||
_ when strategy.StartsWith("UseIPv6") => "prefer_ipv6",
|
||||
_ when strategy.StartsWith("UseIP") => "prefer_ipv4",
|
||||
_ when strategy.StartsWith("ForceIPv6") => "ipv6_only",
|
||||
_ when strategy.StartsWith("ForceIP") => "ipv4_only",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
namespace ServiceLib.Enums;
|
||||
|
||||
public enum EViewAction
|
||||
{
|
||||
CloseWindow,
|
||||
ShowYesNo,
|
||||
SaveFileDialog,
|
||||
AddBatchRoutingRulesYesNo,
|
||||
SetClipboardData,
|
||||
AddServerViaClipboard,
|
||||
ImportRulesFromClipboard,
|
||||
ProfilesFocus,
|
||||
ShareSub,
|
||||
ShareServer,
|
||||
ScanScreenTask,
|
||||
ScanImageTask,
|
||||
BrowseServer,
|
||||
ImportRulesFromFile,
|
||||
InitSettingFont,
|
||||
PasswordInput,
|
||||
SubEditWindow,
|
||||
RoutingRuleSettingWindow,
|
||||
RoutingRuleDetailsWindow,
|
||||
AddServerWindow,
|
||||
AddServer2Window,
|
||||
AddGroupServerWindow,
|
||||
DNSSettingWindow,
|
||||
RoutingSettingWindow,
|
||||
OptionSettingWindow,
|
||||
FullConfigTemplateWindow,
|
||||
GlobalHotkeySettingWindow,
|
||||
SubSettingWindow,
|
||||
DispatcherRefreshServersBiz,
|
||||
DispatcherRefreshIcon,
|
||||
DispatcherShowMsg,
|
||||
}
|
||||
@@ -2,16 +2,9 @@ namespace ServiceLib.Events;
|
||||
|
||||
public static class AppEvents
|
||||
{
|
||||
public static readonly EventChannel<Unit> ReloadRequested = new();
|
||||
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
|
||||
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
||||
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
||||
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
||||
public static readonly EventChannel<bool> HasUpdateNotified = new();
|
||||
|
||||
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
||||
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
||||
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
|
||||
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||
|
||||
public static readonly EventChannel<string> SendSnackMsgRequested = new();
|
||||
@@ -20,12 +13,5 @@ public static class AppEvents
|
||||
public static readonly EventChannel<Unit> AppExitRequested = new();
|
||||
public static readonly EventChannel<bool> ShutdownRequested = new();
|
||||
|
||||
public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new();
|
||||
|
||||
public static readonly EventChannel<string> SetDefaultServerRequested = new();
|
||||
|
||||
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
|
||||
public static readonly EventChannel<Unit> TestServerRequested = new();
|
||||
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
|
||||
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ namespace ServiceLib;
|
||||
|
||||
public class Global
|
||||
{
|
||||
#region const
|
||||
|
||||
public const string AppName = "v2rayN";
|
||||
public const string GithubUrl = "https://github.com";
|
||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||
@@ -106,9 +104,7 @@ public class Global
|
||||
public const string SingboxFakeDNSTag = "fake_dns";
|
||||
|
||||
public const int Hysteria2DefaultHopInt = 30;
|
||||
|
||||
public const string PolicyGroupExcludeKeywords = @"剩余|过期|到期|重置|[Rr]emaining|[Ee]xpir|[Rr]eset";
|
||||
|
||||
public const string PolicyGroupDefaultAllFilter = $"^(?!.*(?:{PolicyGroupExcludeKeywords})).*$";
|
||||
|
||||
public static readonly List<string> PolicyGroupDefaultFilterList =
|
||||
@@ -557,7 +553,6 @@ public class Global
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"fakedns",
|
||||
];
|
||||
|
||||
public static readonly List<int> TunMtus =
|
||||
@@ -737,6 +732,12 @@ public class Global
|
||||
"reply",
|
||||
];
|
||||
|
||||
public static readonly List<string> FakeIPRanges =
|
||||
[
|
||||
"198.18.0.0/15",
|
||||
"11.0.0.0/8",
|
||||
];
|
||||
|
||||
public static readonly List<string> RootCertProviders =
|
||||
[
|
||||
"system",
|
||||
@@ -744,5 +745,27 @@ public class Global
|
||||
MozillaRootProvider,
|
||||
];
|
||||
|
||||
#endregion const
|
||||
public static readonly IReadOnlyList<string> TunIpv4Address =
|
||||
[
|
||||
"172.18.0.1/30",
|
||||
"172.31.0.1/30",
|
||||
"172.20.0.1/30",
|
||||
"172.16.0.1/30",
|
||||
"192.168.100.1/30",
|
||||
"10.10.14.1/30",
|
||||
"10.1.0.1/30",
|
||||
"10.0.0.1/30",
|
||||
];
|
||||
|
||||
public static readonly IReadOnlyList<string> TunIpv6Address =
|
||||
[
|
||||
"fc00::172:18:0:1/128",
|
||||
"fc00::172:31:0:1/128",
|
||||
"fc00::172:20:0:1/128",
|
||||
"fc00::172:16:0:1/128",
|
||||
"fc00::192:168:100:1/128",
|
||||
"fc00::10:10:14:1/128",
|
||||
"fc00::10:1:0:1/128",
|
||||
"fc00::10:0:0:1/128",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ global using ServiceLib.Handler.Fmt;
|
||||
global using ServiceLib.Handler.SysProxy;
|
||||
global using ServiceLib.Helper;
|
||||
global using ServiceLib.Manager;
|
||||
global using ServiceLib.Models.CoreConfigs;
|
||||
global using ServiceLib.Models.Configs;
|
||||
global using ServiceLib.Models.CoreConfigs;
|
||||
global using ServiceLib.Models.Dto;
|
||||
global using ServiceLib.Models.Entities;
|
||||
global using ServiceLib.Resx;
|
||||
|
||||
@@ -193,12 +193,16 @@ public class CoreConfigContextBuilder
|
||||
if (preSocksItem != null)
|
||||
{
|
||||
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
|
||||
|
||||
var protectCoreTypeList = new HashSet<ECoreType>(nodeContext.ProtectCoreTypeList) { nodeContext.RunCoreType };
|
||||
|
||||
return preSocksResult with
|
||||
{
|
||||
Context = preSocksResult.Context with
|
||||
{
|
||||
ProtectDomainList =
|
||||
[.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
|
||||
ProtectCoreTypeList = protectCoreTypeList,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,10 +115,12 @@ public static class ConfigHandler
|
||||
config.ConstItem ??= new ConstItem();
|
||||
|
||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||
config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault();
|
||||
config.SimpleDNSItem.GlobalFakeIp ??= true;
|
||||
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
|
||||
config.SimpleDNSItem.ServeStale ??= false;
|
||||
config.SimpleDNSItem.ParallelQuery ??= false;
|
||||
config.SimpleDNSItem.EnableHappyEyeballs ??= false;
|
||||
|
||||
config.SpeedTestItem ??= new();
|
||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||
@@ -164,14 +166,28 @@ public static class ConfigHandler
|
||||
config.ClashUIItem.ConnectionsColumnItem ??= [];
|
||||
config.SystemProxyItem ??= new();
|
||||
config.WebDavItem ??= new();
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.CheckUpdateItem ??= new();
|
||||
config.Fragment4RayItem ??= new()
|
||||
{
|
||||
Packets = "tlshello",
|
||||
Length = "50-100",
|
||||
Interval = "10-20",
|
||||
MaxSplit = "0"
|
||||
};
|
||||
config.Fragment4RayItem.MaxSplit ??= "0";
|
||||
|
||||
config.HappyEyeballs4RayItem ??= new()
|
||||
{
|
||||
TryDelayMs = 250,
|
||||
PrioritizeIPv6 = false,
|
||||
Interleave = 1,
|
||||
MaxConcurrentTry = 4,
|
||||
};
|
||||
if ((config.Fragment4RayItem.Lengths ?? []).Count == 0)
|
||||
{
|
||||
config.Fragment4RayItem.Lengths = [config.Fragment4RayItem.Length ?? "50-100"];
|
||||
}
|
||||
if ((config.Fragment4RayItem.Delays ?? []).Count == 0)
|
||||
{
|
||||
config.Fragment4RayItem.Delays = [config.Fragment4RayItem.Interval ?? "10-20"];
|
||||
}
|
||||
config.GlobalHotkeys ??= [];
|
||||
|
||||
if (config.SystemProxyItem.SystemProxyExceptions.IsNullOrEmpty())
|
||||
@@ -1511,7 +1527,8 @@ public static class ConfigHandler
|
||||
else if (node.ConfigType == EConfigType.Custom
|
||||
&& node.PreSocksPort is > 0 and <= 65535)
|
||||
{
|
||||
var preCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||
var customPreCoreType = AppManager.Instance.GetCoreType(null, EConfigType.Custom);
|
||||
var preCoreType = (enableLegacyProtect && config.TunModeItem.EnableTun) ? ECoreType.sing_box : customPreCoreType;
|
||||
itemSocks = new ProfileItem()
|
||||
{
|
||||
CoreType = preCoreType,
|
||||
|
||||
@@ -16,7 +16,6 @@ public static class SysProxyHandler
|
||||
try
|
||||
{
|
||||
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (port <= 0)
|
||||
{
|
||||
return false;
|
||||
@@ -24,17 +23,18 @@ public static class SysProxyHandler
|
||||
switch (type)
|
||||
{
|
||||
case ESysProxyType.ForcedChange when Utils.IsWindows():
|
||||
{
|
||||
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
}
|
||||
var (strProxy, strExceptions) = GetWindowsProxyString(config, port);
|
||||
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsLinux():
|
||||
var exceptions = SanitizeExceptions(config);
|
||||
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedChange when Utils.IsMacOS():
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
|
||||
var exceptions2 = SanitizeExceptions(config);
|
||||
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions2);
|
||||
break;
|
||||
|
||||
case ESysProxyType.ForcedClear when Utils.IsWindows():
|
||||
@@ -66,15 +66,31 @@ public static class SysProxyHandler
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void GetWindowsProxyString(Config config, int port, out string strProxy, out string strExceptions)
|
||||
private static string SanitizeExceptions(Config config)
|
||||
{
|
||||
strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
var exceptions = config.SystemProxyItem.SystemProxyExceptions;
|
||||
if (exceptions.IsNullOrEmpty())
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var items = exceptions
|
||||
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(item => item.Replace(" ", string.Empty))
|
||||
.Where(item => item.Length > 0);
|
||||
|
||||
return string.Join(',', items);
|
||||
}
|
||||
|
||||
private static (string strProxy, string strExceptions) GetWindowsProxyString(Config config, int port)
|
||||
{
|
||||
var strExceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
|
||||
if (config.SystemProxyItem.NotProxyLocalAddress)
|
||||
{
|
||||
strExceptions = $"<local>;{strExceptions}";
|
||||
}
|
||||
|
||||
strProxy = string.Empty;
|
||||
var strProxy = string.Empty;
|
||||
if (config.SystemProxyItem.SystemProxyAdvancedProtocol.IsNullOrEmpty())
|
||||
{
|
||||
strProxy = $"{Global.Loopback}:{port}";
|
||||
@@ -86,6 +102,8 @@ public static class SysProxyHandler
|
||||
.Replace("{http_port}", port.ToString())
|
||||
.Replace("{socks_port}", port.ToString());
|
||||
}
|
||||
|
||||
return (strProxy, strExceptions);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
|
||||
@@ -10,6 +10,7 @@ public sealed class AppManager
|
||||
private int? _statePort2;
|
||||
public static AppManager Instance => _instance.Value;
|
||||
public Config Config => _config;
|
||||
public IWindowDialog WindowDialog { get; set; } = null!;
|
||||
|
||||
public int StatePort
|
||||
{
|
||||
@@ -662,7 +663,7 @@ public sealed class AppManager
|
||||
return Global.SsSecuritiesInSingbox;
|
||||
}
|
||||
|
||||
public ECoreType GetCoreType(ProfileItem profileItem, EConfigType eConfigType)
|
||||
public ECoreType GetCoreType(ProfileItem? profileItem, EConfigType eConfigType)
|
||||
{
|
||||
if (profileItem?.CoreType != null)
|
||||
{
|
||||
|
||||
@@ -288,6 +288,7 @@ public class CertPemManager
|
||||
collection.ImportFromPem(pemText);
|
||||
return collection;
|
||||
});
|
||||
|
||||
private static readonly Lazy<X509Certificate2Collection> _mozillaRootCerts = new(() =>
|
||||
{
|
||||
var pemText = EmbedUtils.GetEmbedText(Global.MozillaRootCertFileName);
|
||||
@@ -295,6 +296,7 @@ public class CertPemManager
|
||||
collection.ImportFromPem(pemText);
|
||||
return collection;
|
||||
});
|
||||
|
||||
private X509Certificate2Collection BuildTrustedCertificateCollection()
|
||||
{
|
||||
if (_config.GuiItem.RootCertProvider == Global.ChromeRootProvider)
|
||||
|
||||
@@ -34,7 +34,18 @@ public class CoreAdminManager
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("#!/bin/bash");
|
||||
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
|
||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||
|
||||
// Passing environment variables to the sudo command, here it only xray or sing-box.
|
||||
if (coreInfo.Environment.Count > 0)
|
||||
{
|
||||
var envArgs = string.Join(" ", coreInfo.Environment.Where(kv => kv.Value.IsNotEmpty()).Select(kv => $"{kv.Key}={kv.Value.AppendQuotes()}"));
|
||||
sb.AppendLine($"exec sudo -S -- env {envArgs} {cmdLine}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"exec sudo -S -- {cmdLine}");
|
||||
}
|
||||
|
||||
var shFilePath = await FileUtils.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
||||
|
||||
var procService = new ProcessService(
|
||||
|
||||
@@ -8,8 +8,10 @@ public class CoreManager
|
||||
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||
public static CoreManager Instance => _instance.Value;
|
||||
private Config _config;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private WindowsJobService? _processJob;
|
||||
|
||||
private ProcessService? _processService;
|
||||
private ProcessService? _processPreService;
|
||||
private bool _linuxSudo = false;
|
||||
|
||||
@@ -34,6 +34,7 @@ public class Config
|
||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
||||
public SimpleDNSItem SimpleDNSItem { get; set; }
|
||||
public HappyEyeballs4RayItem HappyEyeballs4RayItem { get; set; }
|
||||
|
||||
#endregion other entities
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ public class UIItem
|
||||
public bool MacOSShowInDock { get; set; }
|
||||
public List<ColumnItem> MainColumnItem { get; set; }
|
||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||
public bool HideColumnIpInfo { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -148,6 +149,8 @@ public class TunModeItem
|
||||
public string IcmpRouting { get; set; }
|
||||
public bool EnableLegacyProtect { get; set; }
|
||||
public List<string>? RouteExcludeAddress { get; set; }
|
||||
public string Ipv4Address { get; set; }
|
||||
public string Ipv6Address { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -248,9 +251,15 @@ public class CheckUpdateItem
|
||||
public class Fragment4RayItem
|
||||
{
|
||||
public string? Packets { get; set; }
|
||||
public string? Length { get; set; }
|
||||
public string? Interval { get; set; }
|
||||
public List<string>? Lengths { get; set; }
|
||||
public List<string>? Delays { get; set; }
|
||||
public string? MaxSplit { get; set; }
|
||||
|
||||
// For migration from old version, remove those properties in the future
|
||||
public string? Length { get; set; }
|
||||
|
||||
public string? Interval { get; set; }
|
||||
// migration end
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -268,14 +277,26 @@ public class SimpleDNSItem
|
||||
public bool? AddCommonHosts { get; set; }
|
||||
public bool? FakeIP { get; set; }
|
||||
public bool? GlobalFakeIp { get; set; }
|
||||
public string? FakeIPRange { get; set; }
|
||||
public bool? BlockBindingQuery { get; set; }
|
||||
public string? DirectDNS { get; set; }
|
||||
public string? RemoteDNS { get; set; }
|
||||
public string? BootstrapDNS { get; set; }
|
||||
public string? Strategy4Freedom { get; set; }
|
||||
public string? Strategy4Proxy { get; set; }
|
||||
public string? Strategy4ProxyDial { get; set; }
|
||||
public bool? ServeStale { get; set; }
|
||||
public bool? ParallelQuery { get; set; }
|
||||
public string? Hosts { get; set; }
|
||||
public string? DirectExpectedIPs { get; set; }
|
||||
public bool? EnableHappyEyeballs { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class HappyEyeballs4RayItem
|
||||
{
|
||||
public int? TryDelayMs { get; set; }
|
||||
public bool? PrioritizeIPv6 { get; set; }
|
||||
public int? Interleave { get; set; }
|
||||
public int? MaxConcurrentTry { get; set; }
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ public record CoreConfigContext
|
||||
// TUN Compatibility
|
||||
public bool IsTunEnabled { get; init; } = false;
|
||||
public HashSet<string> ProtectDomainList { get; init; } = [];
|
||||
// Typically, it is the core of the outbound chain
|
||||
public HashSet<ECoreType> ProtectCoreTypeList { get; init; } = [];
|
||||
|
||||
public bool IsWindows { get; init; }
|
||||
public bool IsMacOS { get; init; }
|
||||
|
||||
@@ -4,6 +4,7 @@ public class V2rayConfig
|
||||
{
|
||||
public Log4Ray log { get; set; }
|
||||
public object dns { get; set; }
|
||||
public FakeDns4Ray? fakedns { get; set; }
|
||||
public List<Inbounds4Ray> inbounds { get; set; }
|
||||
public List<Outbounds4Ray> outbounds { get; set; }
|
||||
public Routing4Ray routing { get; set; }
|
||||
@@ -43,6 +44,12 @@ public class Log4Ray
|
||||
public string? loglevel { get; set; }
|
||||
}
|
||||
|
||||
public class FakeDns4Ray
|
||||
{
|
||||
public string? ipPool { get; set; }
|
||||
public long? poolSize { get; set; }
|
||||
}
|
||||
|
||||
public class Inbounds4Ray
|
||||
{
|
||||
public string tag { get; set; }
|
||||
@@ -136,8 +143,6 @@ public class Outboundsettings4Ray
|
||||
|
||||
public Response4Ray? response { get; set; }
|
||||
|
||||
public string? domainStrategy { get; set; }
|
||||
|
||||
public int? userLevel { get; set; }
|
||||
|
||||
public string? secretKey { get; set; }
|
||||
@@ -146,6 +151,16 @@ public class Outboundsettings4Ray
|
||||
|
||||
public int? port { get; set; }
|
||||
|
||||
public string? user { get; set; }
|
||||
|
||||
public string? pass { get; set; }
|
||||
|
||||
public int? level { get; set; }
|
||||
|
||||
public string? email { get; set; }
|
||||
|
||||
public object? headers { get; set; }
|
||||
|
||||
public List<WireguardPeer4Ray>? peers { get; set; }
|
||||
|
||||
public bool? noKernelTun { get; set; }
|
||||
@@ -276,7 +291,6 @@ public class BalancersItem4Ray
|
||||
public List<string>? selector { get; set; }
|
||||
public BalancersStrategy4Ray? strategy { get; set; }
|
||||
public string? tag { get; set; }
|
||||
public string? fallbackTag { get; set; }
|
||||
}
|
||||
|
||||
public class BalancersStrategy4Ray
|
||||
@@ -507,6 +521,8 @@ public class MaskSettings4Ray
|
||||
|
||||
public string? length { get; set; }
|
||||
public string? delay { get; set; }
|
||||
public List<string>? lengths { get; set; }
|
||||
public List<string>? delays { get; set; }
|
||||
public int? maxSplit { get; set; }
|
||||
|
||||
// noise
|
||||
@@ -538,15 +554,20 @@ public class AccountsItem4Ray
|
||||
|
||||
public class Sockopt4Ray
|
||||
{
|
||||
public string? domainStrategy { get; set; }
|
||||
|
||||
public string? dialerProxy { get; set; }
|
||||
|
||||
[JsonPropertyName("interface")]
|
||||
public string? Interface { get; set; }
|
||||
|
||||
public HappyEyeballs4Ray? happyEyeballs { get; set; }
|
||||
}
|
||||
|
||||
public class FragmentItem4Ray
|
||||
public class HappyEyeballs4Ray
|
||||
{
|
||||
public string? packets { get; set; }
|
||||
public string? length { get; set; }
|
||||
public string? interval { get; set; }
|
||||
public int? tryDelayMs { get; set; }
|
||||
public bool? prioritizeIPv6 { get; set; }
|
||||
public int? interleave { get; set; }
|
||||
public int? maxConcurrentTry { get; set; }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ public record ProtocolExtraItem
|
||||
public bool? Uot { get; init; }
|
||||
public string? CongestionControl { get; init; }
|
||||
|
||||
// http outbound
|
||||
public string? HttpHeaders { get; init; }
|
||||
|
||||
// vmess
|
||||
public string? AlterId { get; init; }
|
||||
public string? VmessSecurity { get; init; }
|
||||
|
||||
83
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
83
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -321,6 +321,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Please enter valid HTTP request headers JSON. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string InvalidHttpOutboundHeaders {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidHttpOutboundHeaders", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Invalid Realm URL. 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3051,6 +3060,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Enable Happy Eyeballs 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbEnableHappyEyeballs {
|
||||
get {
|
||||
return ResourceManager.GetString("TbEnableHappyEyeballs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Requires the UseIP Strategy. When enabled, it attempts IPv4 and IPv6 connections simultaneously and automatically selects the faster available path. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbEnableHappyEyeballsTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbEnableHappyEyeballsTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 DNS via Bridge 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3079,7 +3106,7 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
|
||||
/// 查找类似 Applies globally by default, and built-in FakeIP filtering is only built into sing-box. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbFakeIPTips {
|
||||
get {
|
||||
@@ -3240,6 +3267,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 HTTP headers 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbHttpOutboundHeaders {
|
||||
get {
|
||||
return ResourceManager.GetString("TbHttpOutboundHeaders", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Realm URL 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3312,6 +3348,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Ipv4 Address 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbIpv4Address {
|
||||
get {
|
||||
return ResourceManager.GetString("TbIpv4Address", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Ipv6 Address 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbIpv6Address {
|
||||
get {
|
||||
return ResourceManager.GetString("TbIpv6Address", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Most Stable 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -3510,6 +3564,24 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Proxy Dial Resolution Strategy 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxyDialResolveStrategy {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxyDialResolveStrategy", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Not recommended; may cause routing loops. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TbProxyDialResolveStrategyTip {
|
||||
get {
|
||||
return ResourceManager.GetString("TbProxyDialResolveStrategyTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Public Key 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -4986,6 +5058,15 @@ namespace ServiceLib.Resx {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 Custom HTTP outbound request headers as a JSON object with string or string array values. 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string TipHttpOutboundHeaders {
|
||||
get {
|
||||
return ResourceManager.GetString("TipHttpOutboundHeaders", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 *Default value raw 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -1495,7 +1495,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
|
||||
@@ -1492,7 +1492,7 @@
|
||||
<value>Choisir une config.</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Actif globalement par défaut, avec filtre FakeIP intégré ; ne fonctionne que dans sing-box</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Veuillez ajouter au moins une configuration</value>
|
||||
|
||||
@@ -1495,7 +1495,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
|
||||
@@ -1495,7 +1495,7 @@
|
||||
<value>Pilih profil</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Berlaku secara global secara default, dengan filter FakeIP bawaan (hanya sing-box).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Silakan tambahkan setidaknya satu konfigurasi.</value>
|
||||
|
||||
@@ -1044,6 +1044,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>Congestion control</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP headers</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Custom HTTP outbound request headers as a JSON object with string or string array values.</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>Previous proxy remarks</value>
|
||||
</data>
|
||||
@@ -1495,7 +1501,7 @@
|
||||
<value>Select Profile</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Please Add At Least One Configuration</value>
|
||||
@@ -1806,6 +1812,9 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Invalid Realm URL.</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Please enter valid HTTP request headers JSON.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>Format: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
@@ -1821,4 +1830,22 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>Only applies to the v2rayN GUI's downloads and network requests. Does not affect the core's certificate validation.</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>Proxy Dial Resolution Strategy</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>Not recommended; may cause routing loops.</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>Enable Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>Requires the UseIP Strategy. When enabled, it attempts IPv4 and IPv6 connections simultaneously and automatically selects the faster available path.</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 Address</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 Address</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1044,6 +1044,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>Управление перегрузками</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP-заголовки</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Пользовательские заголовки исходящего HTTP-запроса. Введите JSON-объект, значения которого — строка или массив строк.</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>Псевдоним предыдущего прокси</value>
|
||||
</data>
|
||||
@@ -1495,7 +1501,7 @@
|
||||
<value>Выбрать профиль</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box).</value>
|
||||
<value>Applies globally by default, and built-in FakeIP filtering is only built into sing-box.</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>Добавьте хотя бы одну конфигурацию</value>
|
||||
@@ -1614,6 +1620,9 @@
|
||||
<data name="TbEchConfigList" xml:space="preserve">
|
||||
<value>EchConfigList</value>
|
||||
</data>
|
||||
<data name="TbVerifyPeerCertByName" xml:space="preserve">
|
||||
<value>Проверять сертификат узла по имени</value>
|
||||
</data>
|
||||
<data name="TbFullCertTips" xml:space="preserve">
|
||||
<value>Полный сертификат (цепочка) в формате PEM</value>
|
||||
</data>
|
||||
@@ -1770,4 +1779,55 @@
|
||||
<data name="menuNewUpdate" xml:space="preserve">
|
||||
<value>Доступно обновление</value>
|
||||
</data>
|
||||
<data name="MsgAllowInsecureDeprecated" xml:space="preserve">
|
||||
<value>Предупреждение: 1 августа 2026 г. Xray отключит пропуск проверки сертификата (allowInsecure). Как можно скорее перейдите на привязанный отпечаток сертификата (pinnedPeerCertSha256). После этой даты allowInsecure использовать будет нельзя.</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddress" xml:space="preserve">
|
||||
<value>Адреса, исключаемые из маршрутизации</value>
|
||||
</data>
|
||||
<data name="TbRouteExcludeAddressTip" xml:space="preserve">
|
||||
<value>Разделяйте запятыми (,).</value>
|
||||
</data>
|
||||
<data name="MsgTunRouteExcludeInvalidAddress" xml:space="preserve">
|
||||
<value>Недопустимый адрес в списке исключений маршрутизации TUN: {0}</value>
|
||||
</data>
|
||||
<data name="MsgOptionsConflict" xml:space="preserve">
|
||||
<value>Конфликт между {0} и {1}</value>
|
||||
</data>
|
||||
<data name="TbEnableFinalFragment" xml:space="preserve">
|
||||
<value>Включить финальную фрагментацию (Final Fragment)</value>
|
||||
</data>
|
||||
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
|
||||
<value>Разбивать конец пакетов на более мелкие фрагменты при отправке. Это может влиять на пропускную способность и задержку.</value>
|
||||
</data>
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS через Bridge</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>Обнаружена небезопасная конфигурация: AllowInsecure включён, но сертификат не предоставлен. Это может привести к атаке «человек посередине» (MITM).</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Некорректный Realm URL.</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>Введите корректный JSON заголовков HTTP-запроса.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>Формат: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Размер пакета Gecko (мин/макс)</value>
|
||||
</data>
|
||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||
<value>Если включено, используется sing-box TUN; иначе — xray TUN.</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||
<value>Поставщик корневых сертификатов</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>Применяется только к загрузкам и сетевым запросам графического интерфейса v2rayN. Не влияет на проверку сертификатов ядром.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1041,6 +1041,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>拥塞控制算法</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP 请求头</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>自定义 HTTP 出站请求头,请输入值为字符串或字符串数组的 JSON 对象。</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>前置代理配置别名</value>
|
||||
</data>
|
||||
@@ -1492,7 +1498,7 @@
|
||||
<value>选择配置</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
|
||||
<value>默认全局生效,仅在 sing-box 中内置 FakeIP 过滤。</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>请至少添加一个配置</value>
|
||||
@@ -1803,6 +1809,9 @@
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL 不正确。</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>请填写合法的HTTP请求头JSON。</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>格式:realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
@@ -1818,4 +1827,22 @@
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>仅用于 v2rayN 界面程序的下载及网络请求,不影响核心的证书验证。</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>连接代理解析策略</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>不建议开启,特殊情况可能回环。</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>启用 Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>需配合 UseIP 策略使用。启用后将同时尝试 IPv4 和 IPv6 连接,并自动选择更快可用的路径。</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 地址</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 地址</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1041,6 +1041,12 @@
|
||||
<data name="TbHeaderType8" xml:space="preserve">
|
||||
<value>擁塞控制算法</value>
|
||||
</data>
|
||||
<data name="TbHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>HTTP 請求頭</value>
|
||||
</data>
|
||||
<data name="TipHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>自訂 HTTP 出站請求頭,請輸入值為字串或字串陣列的 JSON 物件。</value>
|
||||
</data>
|
||||
<data name="LvPrevProfile" xml:space="preserve">
|
||||
<value>前置代理節點別名</value>
|
||||
</data>
|
||||
@@ -1492,7 +1498,7 @@
|
||||
<value>選擇節點</value>
|
||||
</data>
|
||||
<data name="TbFakeIPTips" xml:space="preserve">
|
||||
<value>默認全域生效,內置 FakeIP 過濾,僅在 sing-box 中生效</value>
|
||||
<value>默認全局生效,僅在 sing-box 中內置 FakeIP 過濾。</value>
|
||||
</data>
|
||||
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
|
||||
<value>請至少添加一個節點</value>
|
||||
@@ -1794,7 +1800,49 @@
|
||||
<data name="TbEnabletDnsViaProxy" xml:space="preserve">
|
||||
<value>DNS 透過 Bridge</value>
|
||||
</data>
|
||||
<data name="MsgInsecureConfiguration" xml:space="preserve">
|
||||
<value>偵測到不安全的設定:已啟用 AllowInsecure,但未提供憑證。這可能導致中間人攻擊(MITM)</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrl" xml:space="preserve">
|
||||
<value>Realm URL</value>
|
||||
</data>
|
||||
<data name="InvalidHy2RealmUrl" xml:space="preserve">
|
||||
<value>無效的 Realm URL.</value>
|
||||
</data>
|
||||
<data name="TbHy2RealmUrlTip" xml:space="preserve">
|
||||
<value>格式: realm://<token>@<rendezvous-host>[:port]/<realm-name>?stun=<stun-host>[:port]</value>
|
||||
</data>
|
||||
<data name="TbGeckoPacketSize" xml:space="preserve">
|
||||
<value>Gecko 封包大小 (min/max)</value>
|
||||
</data>
|
||||
<data name="TbLegacyProtectTip" xml:space="preserve">
|
||||
<value>啟用則使用 sing-box TUN ,否則使用 xray TUN</value>
|
||||
</data>
|
||||
<data name="InvalidHttpOutboundHeaders" xml:space="preserve">
|
||||
<value>請填寫合法的HTTP請求頭JSON。</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProvider" xml:space="preserve">
|
||||
<value>根憑證提供者</value>
|
||||
</data>
|
||||
<data name="TbRootCertificateProviderTip" xml:space="preserve">
|
||||
<value>僅適用於 v2rayN GUI 的下載與網路請求,不影響核心的憑證驗證</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategy" xml:space="preserve">
|
||||
<value>連線代理解析策略</value>
|
||||
</data>
|
||||
<data name="TbProxyDialResolveStrategyTip" xml:space="preserve">
|
||||
<value>不建議啟用,特殊情況可能造成連線循環</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballs" xml:space="preserve">
|
||||
<value>啟用 Happy Eyeballs</value>
|
||||
</data>
|
||||
<data name="TbEnableHappyEyeballsTip" xml:space="preserve">
|
||||
<value>需搭配 UseIP 策略,啟用後會同時嘗試 IPv4 與 IPv6 連線,並自動選擇速度較快的路徑</value>
|
||||
</data>
|
||||
<data name="TbIpv6Address" xml:space="preserve">
|
||||
<value>Ipv6 位址</value>
|
||||
</data>
|
||||
<data name="TbIpv4Address" xml:space="preserve">
|
||||
<value>Ipv4 位址</value>
|
||||
</data>
|
||||
</root>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -137,12 +137,12 @@ public partial class CoreConfigSingboxService
|
||||
// fake ip
|
||||
if (simpleDnsItem.FakeIP == true)
|
||||
{
|
||||
var fakeipRange = simpleDnsItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : simpleDnsItem.FakeIPRange;
|
||||
var fakeip = new Server4Sbox
|
||||
{
|
||||
tag = Global.SingboxFakeDNSTag,
|
||||
type = "fakeip",
|
||||
inet4_range = "198.18.0.0/15",
|
||||
inet6_range = "fc00::/18",
|
||||
inet4_range = fakeipRange,
|
||||
};
|
||||
_coreConfig.dns.servers.Add(fakeip);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.dns.rules.Add(new()
|
||||
{
|
||||
server = Global.SingboxDirectDNSTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial),
|
||||
domain = context.ProtectDomainList.ToList(),
|
||||
});
|
||||
}
|
||||
@@ -266,7 +266,7 @@ public partial class CoreConfigSingboxService
|
||||
});
|
||||
}
|
||||
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true)
|
||||
if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp != false)
|
||||
{
|
||||
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||
fakeipFilterRule.invert = true;
|
||||
|
||||
@@ -67,9 +67,13 @@ public partial class CoreConfigSingboxService
|
||||
tunInbound.auto_route = _config.TunModeItem.AutoRoute;
|
||||
tunInbound.strict_route = _config.TunModeItem.StrictRoute;
|
||||
tunInbound.stack = _config.TunModeItem.Stack;
|
||||
if (_config.TunModeItem.EnableIPv6Address == false)
|
||||
|
||||
var address = _config.TunModeItem.Ipv4Address.NullIfEmpty() ?? Global.TunIpv4Address.First();
|
||||
tunInbound.address = [address];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
{
|
||||
tunInbound.address = ["172.18.0.1/30"];
|
||||
var address6 = _config.TunModeItem.Ipv6Address.NullIfEmpty() ?? Global.TunIpv6Address.First();
|
||||
tunInbound.address.Add(address6);
|
||||
}
|
||||
tunInbound.route_exclude_address = _config.TunModeItem.RouteExcludeAddress;
|
||||
|
||||
|
||||
@@ -417,11 +417,15 @@ public partial class CoreConfigSingboxService
|
||||
var tls = new Tls4Sbox()
|
||||
{
|
||||
enabled = true,
|
||||
record_fragment = _config.CoreBasicItem.EnableFragment ? true : null,
|
||||
server_name = serverName,
|
||||
insecure = _node.GetAllowInsecure(),
|
||||
alpn = _node.GetAlpn(),
|
||||
};
|
||||
if (_config.CoreBasicItem.EnableFragment == true)
|
||||
{
|
||||
tls.fragment = true;
|
||||
tls.record_fragment = true;
|
||||
}
|
||||
if (_node.Fingerprint.IsNotEmpty())
|
||||
{
|
||||
tls.utls = new Utls4Sbox()
|
||||
|
||||
@@ -10,18 +10,27 @@ public partial class CoreConfigSingboxService
|
||||
var simpleDnsItem = context.SimpleDnsItem;
|
||||
|
||||
var defaultDomainResolverTag = Global.SingboxDirectDNSTag;
|
||||
var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom);
|
||||
var dialDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4ProxyDial);
|
||||
|
||||
var rawDNSItem = context.RawDnsItem;
|
||||
if (rawDNSItem is { Enabled: true })
|
||||
{
|
||||
defaultDomainResolverTag = Global.SingboxLocalDNSTag;
|
||||
directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||
dialDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom;
|
||||
}
|
||||
else if (!simpleDnsItem.Strategy4Freedom.IsNullOrEmpty())
|
||||
{
|
||||
var directOutbound = _coreConfig.outbounds.FirstOrDefault(o => o.tag == Global.DirectTag);
|
||||
directOutbound?.domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom),
|
||||
};
|
||||
}
|
||||
_coreConfig.route.default_domain_resolver = new()
|
||||
{
|
||||
server = defaultDomainResolverTag,
|
||||
strategy = directDnsStrategy
|
||||
strategy = dialDnsStrategy
|
||||
};
|
||||
|
||||
if (context.IsTunEnabled)
|
||||
@@ -34,19 +43,22 @@ public partial class CoreConfigSingboxService
|
||||
_coreConfig.route.rules.AddRange(tunRules);
|
||||
}
|
||||
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.route.rules.Add(new()
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_name = lstDnsExe
|
||||
});
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
port = [53],
|
||||
action = "hijack-dns",
|
||||
process_path = lstDirectExe,
|
||||
});
|
||||
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_name = lstDirectExe
|
||||
});
|
||||
_coreConfig.route.rules.Add(new()
|
||||
{
|
||||
outbound = Global.DirectTag,
|
||||
process_path = lstDirectExe,
|
||||
});
|
||||
}
|
||||
|
||||
// ICMP Routing
|
||||
var icmpRouting = _config.TunModeItem.IcmpRouting ?? "";
|
||||
@@ -256,34 +268,38 @@ public partial class CoreConfigSingboxService
|
||||
}
|
||||
}
|
||||
|
||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||
private List<string> BuildRoutingDirectExe()
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
foreach (var coreConfig in allCoreInfo)
|
||||
{
|
||||
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coreConfig.CoreExes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.sing_box)
|
||||
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||
if (!exePath.IsNullOrEmpty())
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
directExeSet.Add(exePath);
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
return directExeSet.ToList();
|
||||
}
|
||||
|
||||
private void GenRoutingUserRule(RulesItem? item)
|
||||
|
||||
@@ -96,18 +96,19 @@ public partial class CoreConfigV2rayService
|
||||
var balancer = new BalancersItem4Ray
|
||||
{
|
||||
selector = [selector],
|
||||
strategy = strategyType == "leastLoad" ? new()
|
||||
strategy = new()
|
||||
{
|
||||
type = strategyType,
|
||||
settings = new()
|
||||
{
|
||||
expected = 1,
|
||||
tolerance = multipleLoad == EMultipleLoad.Fallback ? 0.2 : null,
|
||||
maxRTT = multipleLoad == EMultipleLoad.Fallback ? "5000ms" : null,
|
||||
},
|
||||
} : null,
|
||||
settings = strategyType == "leastLoad"
|
||||
? new()
|
||||
{
|
||||
expected = 1,
|
||||
tolerance = multipleLoad == EMultipleLoad.Fallback ? 0.2 : null,
|
||||
maxRTT = multipleLoad == EMultipleLoad.Fallback ? "5000ms" : null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
tag = balancerTag,
|
||||
fallbackTag = _coreConfig.outbounds?.FirstOrDefault(o => o.tag.StartsWith(selector))?.tag,
|
||||
};
|
||||
_coreConfig.routing.balancers ??= [];
|
||||
_coreConfig.routing.balancers.Add(balancer);
|
||||
|
||||
@@ -28,7 +28,7 @@ public partial class CoreConfigV2rayService
|
||||
_coreConfig.routing.rules.Add(new RulesItem4Ray
|
||||
{
|
||||
type = "field",
|
||||
inboundTag = new List<string> { Global.DnsTag },
|
||||
inboundTag = [Global.DnsTag],
|
||||
outboundTag = Global.ProxyTag,
|
||||
});
|
||||
return;
|
||||
@@ -43,11 +43,7 @@ public partial class CoreConfigV2rayService
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = strategy4Freedom,
|
||||
userLevel = 0
|
||||
};
|
||||
FillSockoptDomainStrategy(outbound, strategy4Freedom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +60,22 @@ public partial class CoreConfigV2rayService
|
||||
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
|
||||
}
|
||||
|
||||
var strategy4DialProxy = simpleDnsItem?.Strategy4ProxyDial ?? Global.AsIs;
|
||||
//Outbound DialProxy domainStrategy
|
||||
if (strategy4DialProxy.IsNotEmpty() && strategy4DialProxy != Global.AsIs)
|
||||
{
|
||||
var xraySupportConfigTypeNames = Global.XraySupportConfigType
|
||||
.Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x])
|
||||
.ToHashSet();
|
||||
_coreConfig.outbounds
|
||||
.Where(t => xraySupportConfigTypeNames.Contains(t.protocol))
|
||||
.ToList()
|
||||
.ForEach(outbound =>
|
||||
{
|
||||
FillSockoptDomainStrategy(outbound, strategy4DialProxy);
|
||||
});
|
||||
}
|
||||
|
||||
FillDnsServers(dnsItem);
|
||||
FillDnsHosts(dnsItem);
|
||||
|
||||
@@ -108,6 +120,33 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
private void GenFakeDns()
|
||||
{
|
||||
var fakeipRange = _config.SimpleDNSItem.FakeIPRange.IsNullOrEmpty() ? Global.FakeIPRanges.First() : _config.SimpleDNSItem.FakeIPRange;
|
||||
var poolSize = 65535L;
|
||||
try
|
||||
{
|
||||
var fakeipNetwork = IPNetwork2.Parse(fakeipRange);
|
||||
var totalIPs = fakeipNetwork.Total;
|
||||
// see https://github.com/XTLS/Xray-core/blob/6e3322d219140a025285ded1114fe17a5edb74d8/app/dns/fakedns/fake.go#L88
|
||||
// if math.Log2(float64(lruSize)) >= float64(rooms) { return errors.New("LRU size is bigger than subnet size").AtError() }
|
||||
totalIPs -= 1;
|
||||
if (totalIPs > 0)
|
||||
{
|
||||
poolSize = (totalIPs >= long.MaxValue) ? long.MaxValue : (long)totalIPs;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
_coreConfig.fakedns = new()
|
||||
{
|
||||
ipPool = fakeipRange,
|
||||
poolSize = poolSize,
|
||||
};
|
||||
}
|
||||
|
||||
private void FillDnsServers(Dns4Ray dnsItem)
|
||||
{
|
||||
var simpleDNSItem = context.SimpleDnsItem;
|
||||
@@ -234,6 +273,23 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
var directDnsTagIndex = 1;
|
||||
|
||||
if (simpleDNSItem.FakeIP == true)
|
||||
{
|
||||
var fakeIPMatchDomain = new HashSet<string>(proxyDomainList);
|
||||
fakeIPMatchDomain.UnionWith(proxyGeositeList);
|
||||
if (simpleDNSItem.GlobalFakeIp != false)
|
||||
{
|
||||
fakeIPMatchDomain.UnionWith(directDomainList);
|
||||
fakeIPMatchDomain.UnionWith(directGeositeList);
|
||||
fakeIPMatchDomain.UnionWith(expectedDomainList);
|
||||
}
|
||||
if (fakeIPMatchDomain.Count > 0)
|
||||
{
|
||||
GenFakeDns();
|
||||
AddDnsServers(["fakedns"], fakeIPMatchDomain.ToList());
|
||||
}
|
||||
}
|
||||
|
||||
AddDnsServers(remoteDNSAddress, proxyDomainList);
|
||||
AddDnsServers(directDNSAddress, directDomainList, true);
|
||||
AddDnsServers(remoteDNSAddress, proxyGeositeList);
|
||||
@@ -384,11 +440,9 @@ public partial class CoreConfigV2rayService
|
||||
var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag });
|
||||
if (outbound != null)
|
||||
{
|
||||
outbound.settings = new()
|
||||
{
|
||||
domainStrategy = domainStrategy4Freedom,
|
||||
userLevel = 0,
|
||||
};
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
outbound.streamSettings.sockopt.domainStrategy = domainStrategy4Freedom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,4 +525,28 @@ public partial class CoreConfigV2rayService
|
||||
};
|
||||
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
|
||||
}
|
||||
|
||||
private void FillSockoptDomainStrategy(Outbounds4Ray outbound, string? domainStrategy, bool skipHappyEyeballs = false)
|
||||
{
|
||||
if (domainStrategy.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
outbound.streamSettings ??= new();
|
||||
outbound.streamSettings.sockopt ??= new();
|
||||
var sockopt = outbound.streamSettings.sockopt;
|
||||
sockopt.domainStrategy = domainStrategy;
|
||||
|
||||
if (skipHappyEyeballs || _config.SimpleDNSItem.EnableHappyEyeballs != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var happyEyeballsItem = _config.HappyEyeballs4RayItem;
|
||||
sockopt.happyEyeballs ??= new();
|
||||
var happyEyeballs = sockopt.happyEyeballs;
|
||||
happyEyeballs.tryDelayMs = happyEyeballsItem.TryDelayMs;
|
||||
happyEyeballs.prioritizeIPv6 = happyEyeballsItem.PrioritizeIPv6;
|
||||
happyEyeballs.interleave = happyEyeballsItem.Interleave;
|
||||
happyEyeballs.maxConcurrentTry = happyEyeballsItem.MaxConcurrentTry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,23 @@ public partial class CoreConfigV2rayService
|
||||
new Inbounds4Ray();
|
||||
tunInbound.settings.name = context.IsMacOS ? $"utun{new Random().Next(99)}" : "xray_tun";
|
||||
tunInbound.settings.MTU = _config.TunModeItem.Mtu;
|
||||
if (!_config.TunModeItem.EnableIPv6Address)
|
||||
|
||||
var address = _config.TunModeItem.Ipv4Address.NullIfEmpty() ?? Global.TunIpv4Address.First();
|
||||
tunInbound.settings.gateway = [address];
|
||||
if (_config.TunModeItem.EnableIPv6Address == true)
|
||||
{
|
||||
tunInbound.settings.gateway = ["172.18.0.1/30"];
|
||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
||||
var address6 = _config.TunModeItem.Ipv6Address.NullIfEmpty() ?? Global.TunIpv6Address.First();
|
||||
tunInbound.settings.gateway.Add(address6);
|
||||
}
|
||||
tunInbound.settings.dns = [address.Split('/').First()];
|
||||
tunInbound.settings.autoSystemRoutingTable = ["0.0.0.0/0"];
|
||||
var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx();
|
||||
if (!bindInterface.IsNullOrEmpty())
|
||||
{
|
||||
tunInbound.settings.autoOutboundsInterface = bindInterface;
|
||||
}
|
||||
tunInbound.sniffing = inbound.sniffing;
|
||||
tunInbound.sniffing.routeOnly = true;
|
||||
|
||||
if (_config.TunModeItem.RouteExcludeAddress is { Count: > 0 })
|
||||
{
|
||||
@@ -151,6 +157,16 @@ public partial class CoreConfigV2rayService
|
||||
inbound.sniffing.destOverride = inItem.DestOverride;
|
||||
inbound.sniffing.routeOnly = inItem.RouteOnly;
|
||||
|
||||
if (_config.SimpleDNSItem.FakeIP == true)
|
||||
{
|
||||
// Ensure destOverride contains "fakedns" if FakeIP is enabled
|
||||
inbound.sniffing.destOverride ??= [];
|
||||
if (!inbound.sniffing.destOverride.Contains("fakedns"))
|
||||
{
|
||||
inbound.sniffing.destOverride.Add("fakedns");
|
||||
}
|
||||
}
|
||||
|
||||
return inbound;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public partial class CoreConfigV2rayService
|
||||
break;
|
||||
}
|
||||
case EConfigType.SOCKS:
|
||||
case EConfigType.HTTP:
|
||||
{
|
||||
ServersItem4Ray serversItem;
|
||||
if (outbound.settings.servers.Count <= 0)
|
||||
@@ -171,6 +170,31 @@ public partial class CoreConfigV2rayService
|
||||
outbound.settings.vnext = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.HTTP:
|
||||
{
|
||||
outbound.settings.address = _node.Address;
|
||||
outbound.settings.port = _node.Port;
|
||||
|
||||
if (protocolExtra.HttpHeaders.IsNotEmpty())
|
||||
{
|
||||
outbound.settings.headers = JsonUtils.ParseJson(protocolExtra.HttpHeaders);
|
||||
}
|
||||
|
||||
if (_node.Username.IsNotEmpty()
|
||||
&& _node.Password.IsNotEmpty())
|
||||
{
|
||||
outbound.settings.user = _node.Username;
|
||||
outbound.settings.pass = _node.Password;
|
||||
outbound.settings.level = 1;
|
||||
outbound.settings.email = Global.UserEMail;
|
||||
}
|
||||
|
||||
FillOutboundMux(outbound);
|
||||
|
||||
outbound.settings.vnext = null;
|
||||
outbound.settings.servers = null;
|
||||
break;
|
||||
}
|
||||
case EConfigType.VLESS:
|
||||
{
|
||||
VnextItem4Ray vnextItem;
|
||||
@@ -841,21 +865,10 @@ public partial class CoreConfigV2rayService
|
||||
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
|
||||
.ToList();
|
||||
|
||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
||||
var fragmentMask = BuildFragmentsMasks();
|
||||
|
||||
foreach (var outbound in actOutboundWithTlsList)
|
||||
{
|
||||
//var packets = configPackets;
|
||||
//if (outbound.streamSettings.security == Global.StreamSecurityReality
|
||||
// && packets == "tlshello")
|
||||
//{
|
||||
// packets = "1-3";
|
||||
//}
|
||||
//else if (outbound.streamSettings.security == Global.StreamSecurity
|
||||
// && packets != "tlshello")
|
||||
//{
|
||||
// packets = "tlshello";
|
||||
//}
|
||||
var finalMaskJsonObj = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings?.finalmask)) as JsonObject ?? new JsonObject();
|
||||
// tcp fragment
|
||||
var tcpFinalmaskList = finalMaskJsonObj["tcp"] as JsonArray ?? [];
|
||||
@@ -864,13 +877,6 @@ public partial class CoreConfigV2rayService
|
||||
tcpFinalmaskList.Add(JsonUtils.SerializeToNode(fragmentMask));
|
||||
finalMaskJsonObj["tcp"] = tcpFinalmaskList;
|
||||
}
|
||||
// udp noise
|
||||
var udpFinalmaskList = finalMaskJsonObj["udp"] as JsonArray ?? [];
|
||||
if (udpFinalmaskList.Count == 0)
|
||||
{
|
||||
udpFinalmaskList.Add(JsonUtils.SerializeToNode(noiseMask));
|
||||
finalMaskJsonObj["udp"] = udpFinalmaskList;
|
||||
}
|
||||
// write back
|
||||
outbound.streamSettings.finalmask = finalMaskJsonObj;
|
||||
}
|
||||
@@ -878,7 +884,7 @@ public partial class CoreConfigV2rayService
|
||||
|
||||
private void ApplyFinalFragment()
|
||||
{
|
||||
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
|
||||
var fragmentMask = BuildFragmentsMasks();
|
||||
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
|
||||
|
||||
var fragmentFreedom = new Outbounds4Ray()
|
||||
@@ -890,9 +896,8 @@ public partial class CoreConfigV2rayService
|
||||
finalmask = new Finalmask4Ray
|
||||
{
|
||||
tcp = [fragmentMask],
|
||||
udp = [noiseMask],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var outbound in actOutboundList)
|
||||
@@ -910,13 +915,22 @@ public partial class CoreConfigV2rayService
|
||||
}
|
||||
}
|
||||
|
||||
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
|
||||
private Mask4Ray BuildFragmentsMasks()
|
||||
{
|
||||
var configPackets = _config.Fragment4RayItem?.Packets.NullIfEmpty() ?? "tlshello";
|
||||
var configLength = _config.Fragment4RayItem?.Length.NullIfEmpty() ?? "50-100";
|
||||
var configDelay = _config.Fragment4RayItem?.Interval.NullIfEmpty() ?? "10-20";
|
||||
var configLengths = _config.Fragment4RayItem?.Lengths ?? [];
|
||||
var configDelays = _config.Fragment4RayItem?.Delays ?? [];
|
||||
var configMaxSplit = _config.Fragment4RayItem?.MaxSplit.NullIfEmpty() ?? "0";
|
||||
|
||||
if (configLengths.Count == 0)
|
||||
{
|
||||
configLengths = ["50-100"];
|
||||
}
|
||||
if (configDelays.Count == 0)
|
||||
{
|
||||
configDelays = ["10-20"];
|
||||
}
|
||||
|
||||
var maxSplit = 0;
|
||||
var parts = configMaxSplit.Split('-');
|
||||
if (parts.Length > 0 && int.TryParse(parts[0], out var ms))
|
||||
@@ -930,21 +944,15 @@ public partial class CoreConfigV2rayService
|
||||
settings = new MaskSettings4Ray
|
||||
{
|
||||
packets = configPackets,
|
||||
length = configLength,
|
||||
delay = configDelay,
|
||||
lengths = configLengths,
|
||||
delays = configDelays,
|
||||
maxSplit = maxSplit,
|
||||
}
|
||||
};
|
||||
var noiseMask = new Mask4Ray
|
||||
{
|
||||
type = "noise",
|
||||
settings = new MaskSettings4Ray
|
||||
{
|
||||
length = "10-20",
|
||||
delay = "10-16",
|
||||
}
|
||||
// For legacy xray compatibility, remove this in the future
|
||||
length = configLengths.FirstOrDefault(),
|
||||
delay = configDelays.FirstOrDefault(),
|
||||
},
|
||||
};
|
||||
|
||||
return (fragmentMask, noiseMask);
|
||||
return fragmentMask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,21 @@ public partial class CoreConfigV2rayService
|
||||
{
|
||||
_coreConfig.routing.rules.AddRange(tunRules);
|
||||
}
|
||||
var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe();
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
var lstDirectExe = BuildRoutingDirectExe();
|
||||
if (lstDirectExe.Count > 0)
|
||||
{
|
||||
port = "53",
|
||||
process = lstDnsExe,
|
||||
outboundTag = Global.DnsOutboundTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
port = "53",
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DnsOutboundTag,
|
||||
});
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
process = lstDirectExe,
|
||||
outboundTag = Global.DirectTag,
|
||||
});
|
||||
}
|
||||
_coreConfig.routing.rules.Add(new()
|
||||
{
|
||||
inboundTag = ["tun"],
|
||||
@@ -232,36 +235,44 @@ public partial class CoreConfigV2rayService
|
||||
return finalRule;
|
||||
}
|
||||
|
||||
private static (List<string> lstDnsExe, List<string> lstDirectExe) BuildRoutingDirectExe()
|
||||
private List<string> BuildRoutingDirectExe()
|
||||
{
|
||||
var dnsExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var directExeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var coreInfoResult = CoreInfoManager.Instance.GetCoreInfo();
|
||||
var allCoreInfo = CoreInfoManager.Instance.GetCoreInfo();
|
||||
|
||||
foreach (var coreConfig in coreInfoResult)
|
||||
foreach (var coreConfig in allCoreInfo)
|
||||
{
|
||||
if (!context.ProtectCoreTypeList.Contains(coreConfig.CoreType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.v2rayN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coreConfig.CoreExes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (coreConfig.CoreType == ECoreType.Xray)
|
||||
{
|
||||
directExeSet.Add("xray/");
|
||||
continue;
|
||||
}
|
||||
foreach (var baseExeName in coreConfig.CoreExes)
|
||||
{
|
||||
if (coreConfig.CoreType != ECoreType.Xray)
|
||||
//directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
var exePath = CoreInfoManager.Instance.GetCoreExecFile(coreConfig, out _);
|
||||
if (!exePath.IsNullOrEmpty())
|
||||
{
|
||||
dnsExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
directExeSet.Add(exePath);
|
||||
}
|
||||
directExeSet.Add(Utils.GetExeName(baseExeName));
|
||||
}
|
||||
}
|
||||
|
||||
directExeSet.Add("xray/");
|
||||
//directExeSet.Add("xray/");
|
||||
directExeSet.Add("self/");
|
||||
|
||||
var lstDnsExe = new List<string>(dnsExeSet);
|
||||
var lstDirectExe = new List<string>(directExeSet);
|
||||
|
||||
return (lstDnsExe, lstDirectExe);
|
||||
return directExeSet.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace ServiceLib.Services;
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||
ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime);
|
||||
await UpdateFunc(it.IndexId, responseTime.ToString());
|
||||
|
||||
if (responseTime > 0)
|
||||
if (!_config.UiItem.HideColumnIpInfo && responseTime > 0)
|
||||
{
|
||||
var ipInfo = await ConnectionHandler.GetIPInfo(webProxy);
|
||||
var ipStr = ipInfo?.ToString() ?? Global.None;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddGroupServerViewModel : MyReactiveObject
|
||||
public class AddGroupServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
@@ -29,7 +31,7 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
|
||||
public IObservableCollection<ProfileItem> AllProfilePreviewItemsObs { get; } = new ObservableCollectionExtended<ProfileItem>();
|
||||
|
||||
//public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> RemoveCmd { get; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
|
||||
@@ -39,15 +41,18 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public AddGroupServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddGroupServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedChild,
|
||||
SelectedChild => SelectedChild != null && !SelectedChild.Remarks.IsNullOrEmpty());
|
||||
selectedChild => selectedChild != null && !selectedChild.Remarks.IsNullOrEmpty());
|
||||
|
||||
AddCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await AddChildAsync();
|
||||
});
|
||||
RemoveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await ChildRemoveAsync();
|
||||
@@ -103,6 +108,20 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
ChildItemsObs.AddRange(childItemList);
|
||||
}
|
||||
|
||||
public async Task AddChildAsync()
|
||||
{
|
||||
var profileSelectViewModel = new ProfilesSelectViewModel();
|
||||
profileSelectViewModel.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
profileSelectViewModel.MultiSelect = true;
|
||||
var result = await AppManager.Instance.WindowDialog.ShowDialogAsync(profileSelectViewModel);
|
||||
if (result != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var profiles = await profileSelectViewModel.GetProfileItems() ?? [];
|
||||
ChildItemsObs.AddRange(profiles);
|
||||
}
|
||||
|
||||
public async Task ChildRemoveAsync()
|
||||
{
|
||||
if (SelectedChild == null || SelectedChild.IndexId.IsNullOrEmpty())
|
||||
@@ -230,7 +249,7 @@ public class AddGroupServerViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServer2ViewModel : MyReactiveObject
|
||||
public class AddServer2ViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<Unit, string?> BrowseConfigFileInteraction { get; } = new();
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
@@ -13,15 +17,18 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> SaveServerCmd { get; }
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
public AddServer2ViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddServer2ViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
BrowseServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.BrowseServer, null);
|
||||
await Task.CompletedTask;
|
||||
var fileName = await BrowseConfigFileInteraction.Handle(Unit.Default);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
await BrowseServer(fileName);
|
||||
});
|
||||
EditServerCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -55,7 +62,7 @@ public class AddServer2ViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.EditCustomServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class AddServerViewModel : MyReactiveObject
|
||||
public class AddServerViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public ProfileItem SelectedSource { get; set; }
|
||||
|
||||
@@ -80,6 +82,9 @@ public class AddServerViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool NaiveQuic { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string HttpHeadersJson { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string Hy2RealmUrl { get; set; }
|
||||
|
||||
@@ -238,10 +243,9 @@ public class AddServerViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public AddServerViewModel(ProfileItem profileItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public AddServerViewModel(ProfileItem profileItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
FetchCertCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -255,7 +259,6 @@ public class AddServerViewModel : MyReactiveObject
|
||||
{
|
||||
await SaveServerAsync();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(x => x.Cert)
|
||||
.Subscribe(_ => UpdateCertTip());
|
||||
|
||||
@@ -311,6 +314,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||
CongestionControl = protocolExtra.CongestionControl ?? string.Empty;
|
||||
InsecureConcurrency = protocolExtra.InsecureConcurrency > 0 ? protocolExtra.InsecureConcurrency : null;
|
||||
NaiveQuic = protocolExtra.NaiveQuic ?? false;
|
||||
HttpHeadersJson = protocolExtra.HttpHeaders ?? string.Empty;
|
||||
Hy2RealmUrl = protocolExtra.Hy2RealmUrl ?? string.Empty;
|
||||
GeckoMinPacketSize = protocolExtra.GeckoMinPacketSize.ToInt();
|
||||
GeckoMaxPacketSize = protocolExtra.GeckoMaxPacketSize.ToInt();
|
||||
@@ -379,6 +383,11 @@ public class AddServerViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (HttpHeadersJson.IsNotEmpty() && JsonUtils.ParseJson(HttpHeadersJson) == null)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.InvalidHttpOutboundHeaders);
|
||||
return;
|
||||
}
|
||||
SelectedSource.CoreType = CoreType.IsNullOrEmpty() ? null : Enum.Parse<ECoreType>(CoreType);
|
||||
SelectedSource.AllowInsecure = AllowInsecure ? Global.StringTrue : Global.StringFalse;
|
||||
SelectedSource.MuxEnabled = MuxEnabled;
|
||||
@@ -416,6 +425,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||
VmessSecurity = VmessSecurity.NullIfEmpty(),
|
||||
VlessEncryption = VlessEncryption.NullIfEmpty(),
|
||||
SsMethod = SsMethod.NullIfEmpty(),
|
||||
HttpHeaders = SelectedSource.ConfigType == EConfigType.HTTP ? HttpHeadersJson.NullIfEmpty() : null,
|
||||
WgPublicKey = WgPublicKey.NullIfEmpty(),
|
||||
WgPresharedKey = WgPresharedKey.NullIfEmpty(),
|
||||
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
|
||||
@@ -434,7 +444,7 @@ public class AddServerViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -13,12 +13,11 @@ public class BackupAndRestoreViewModel : MyReactiveObject
|
||||
public WebDavItem SelectedSource { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string OperationMsg { get; set; }
|
||||
public string OperationMsg { get; set; } = string.Empty;
|
||||
|
||||
public BackupAndRestoreViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public BackupAndRestoreViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
WebDavCheckCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
|
||||
@@ -7,15 +7,16 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
private List<CheckUpdateModel> _lstUpdated = [];
|
||||
private static readonly string _tag = "CheckUpdateViewModel";
|
||||
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
|
||||
public IObservableCollection<CheckUpdateModel> CheckUpdateModels { get; } = new ObservableCollectionExtended<CheckUpdateModel>();
|
||||
public ReactiveCommand<Unit, Unit> CheckUpdateCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> CheckOnlyCmd { get; }
|
||||
[Reactive] public bool EnableCheckPreReleaseUpdate { get; set; }
|
||||
|
||||
public CheckUpdateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public CheckUpdateViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
CheckUpdateCmd = ReactiveCommand.CreateFromTask(CheckUpdate);
|
||||
CheckUpdateCmd.ThrownExceptions.Subscribe(ex =>
|
||||
@@ -299,7 +300,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
||||
{
|
||||
if (blReload)
|
||||
{
|
||||
AppEvents.ReloadRequested.Publish();
|
||||
ReloadRequested.Publish();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -16,10 +16,9 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashConnectionsViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ClashConnectionsViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
AutoRefresh = _config.ClashUIItem.ConnectionsAutoRefresh;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
|
||||
@@ -33,10 +33,9 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public ClashProxiesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ClashProxiesViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
ProxiesReloadCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -86,15 +85,6 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||
|
||||
#endregion WhenAnyValue && ReactiveCommand
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ProxiesReloadRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await ProxiesReload());
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class DNSSettingViewModel : MyReactiveObject
|
||||
public class DNSSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
[Reactive] public bool? UseSystemHosts { get; set; }
|
||||
[Reactive] public bool? AddCommonHosts { get; set; }
|
||||
[Reactive] public bool? FakeIP { get; set; }
|
||||
[Reactive] public bool? BlockBindingQuery { get; set; }
|
||||
[Reactive] public string? DirectDNS { get; set; }
|
||||
[Reactive] public string? RemoteDNS { get; set; }
|
||||
[Reactive] public string? BootstrapDNS { get; set; }
|
||||
[Reactive] public string? Strategy4Freedom { get; set; }
|
||||
[Reactive] public string? Strategy4Proxy { get; set; }
|
||||
[Reactive] public string? Hosts { get; set; }
|
||||
[Reactive] public string? DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool? ParallelQuery { get; set; }
|
||||
[Reactive] public bool? ServeStale { get; set; }
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive] public bool UseSystemHosts { get; set; }
|
||||
[Reactive] public bool AddCommonHosts { get; set; }
|
||||
[Reactive] public bool FakeIP { get; set; }
|
||||
[Reactive] public string FakeIPRange { get; set; }
|
||||
[Reactive] public bool BlockBindingQuery { get; set; }
|
||||
[Reactive] public string DirectDNS { get; set; }
|
||||
[Reactive] public string RemoteDNS { get; set; }
|
||||
[Reactive] public string BootstrapDNS { get; set; }
|
||||
[Reactive] public string Strategy4Freedom { get; set; }
|
||||
[Reactive] public string Strategy4Proxy { get; set; }
|
||||
[Reactive] public string Strategy4ProxyDial { get; set; }
|
||||
[Reactive] public string Hosts { get; set; }
|
||||
[Reactive] public string DirectExpectedIPs { get; set; }
|
||||
[Reactive] public bool ParallelQuery { get; set; }
|
||||
[Reactive] public bool ServeStale { get; set; }
|
||||
[Reactive] public bool EnableHappyEyeballs { get; set; }
|
||||
|
||||
[Reactive] public bool UseSystemHostsCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; }
|
||||
[Reactive] public string DomainDNSAddressCompatible { get; set; }
|
||||
[Reactive] public string NormalDNSCompatible { get; set; }
|
||||
[Reactive] public string TunDNSCompatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string DomainDNSAddressCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string NormalDNSCompatible { get; set; } = string.Empty;
|
||||
[Reactive] public string TunDNSCompatible { get; set; } = string.Empty;
|
||||
|
||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; }
|
||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; }
|
||||
[Reactive] public string NormalDNS2Compatible { get; set; }
|
||||
[Reactive] public string TunDNS2Compatible { get; set; }
|
||||
[Reactive] public string DomainStrategy4Freedom2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string DomainDNSAddress2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string NormalDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public string TunDNS2Compatible { get; set; } = string.Empty;
|
||||
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
||||
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
||||
|
||||
@@ -35,10 +40,9 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||
|
||||
public DNSSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public DNSSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(SaveSettingAsync);
|
||||
|
||||
ImportDefConfig4V2rayCompatibleCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
@@ -66,20 +70,22 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
var item = _config.SimpleDNSItem;
|
||||
UseSystemHosts = item.UseSystemHosts;
|
||||
AddCommonHosts = item.AddCommonHosts;
|
||||
FakeIP = item.FakeIP;
|
||||
BlockBindingQuery = item.BlockBindingQuery;
|
||||
DirectDNS = item.DirectDNS;
|
||||
RemoteDNS = item.RemoteDNS;
|
||||
BootstrapDNS = item.BootstrapDNS;
|
||||
Strategy4Freedom = item.Strategy4Freedom;
|
||||
Strategy4Proxy = item.Strategy4Proxy;
|
||||
Hosts = item.Hosts;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs;
|
||||
ParallelQuery = item.ParallelQuery;
|
||||
ServeStale = item.ServeStale;
|
||||
|
||||
UseSystemHosts = item.UseSystemHosts ?? false;
|
||||
AddCommonHosts = item.AddCommonHosts ?? false;
|
||||
FakeIP = item.FakeIP ?? false;
|
||||
FakeIPRange = item.FakeIPRange ?? string.Empty;
|
||||
BlockBindingQuery = item.BlockBindingQuery ?? false;
|
||||
DirectDNS = item.DirectDNS ?? string.Empty;
|
||||
RemoteDNS = item.RemoteDNS ?? string.Empty;
|
||||
BootstrapDNS = item.BootstrapDNS ?? string.Empty;
|
||||
Strategy4Freedom = item.Strategy4Freedom ?? string.Empty;
|
||||
Strategy4Proxy = item.Strategy4Proxy ?? string.Empty;
|
||||
Strategy4ProxyDial = item.Strategy4ProxyDial ?? string.Empty;
|
||||
Hosts = item.Hosts ?? string.Empty;
|
||||
DirectExpectedIPs = item.DirectExpectedIPs ?? string.Empty;
|
||||
ParallelQuery = item.ParallelQuery ?? false;
|
||||
ServeStale = item.ServeStale ?? false;
|
||||
EnableHappyEyeballs = item.EnableHappyEyeballs ?? false;
|
||||
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
|
||||
RayCustomDNSEnableCompatible = item1.Enabled;
|
||||
UseSystemHostsCompatible = item1.UseSystemHosts;
|
||||
@@ -101,17 +107,19 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
_config.SimpleDNSItem.UseSystemHosts = UseSystemHosts;
|
||||
_config.SimpleDNSItem.AddCommonHosts = AddCommonHosts;
|
||||
_config.SimpleDNSItem.FakeIP = FakeIP;
|
||||
_config.SimpleDNSItem.FakeIPRange = FakeIPRange;
|
||||
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
|
||||
_config.SimpleDNSItem.DirectDNS = DirectDNS;
|
||||
_config.SimpleDNSItem.RemoteDNS = RemoteDNS;
|
||||
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
|
||||
_config.SimpleDNSItem.Strategy4Freedom = Strategy4Freedom;
|
||||
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
|
||||
_config.SimpleDNSItem.Strategy4ProxyDial = Strategy4ProxyDial;
|
||||
_config.SimpleDNSItem.Hosts = Hosts;
|
||||
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
|
||||
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
|
||||
_config.SimpleDNSItem.ServeStale = ServeStale;
|
||||
|
||||
_config.SimpleDNSItem.EnableHappyEyeballs = EnableHappyEyeballs;
|
||||
if (NormalDNSCompatible.IsNotEmpty())
|
||||
{
|
||||
var obj = JsonUtils.ParseJson(NormalDNSCompatible);
|
||||
@@ -183,9 +191,6 @@ public class DNSSettingViewModel : MyReactiveObject
|
||||
await ConfigHandler.SaveDNSItems(_config, item2);
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
if (_updateView != null)
|
||||
{
|
||||
await _updateView(EViewAction.CloseWindow, null);
|
||||
}
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public class FullConfigTemplateViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Reactive
|
||||
|
||||
[Reactive]
|
||||
@@ -11,16 +13,16 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public bool EnableFullConfigTemplate4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Ray { get; set; }
|
||||
public string FullConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Ray { get; set; }
|
||||
public string FullTunConfigTemplate4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullConfigTemplate4Singbox { get; set; }
|
||||
public string FullConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string FullTunConfigTemplate4Singbox { get; set; }
|
||||
public string FullTunConfigTemplate4Singbox { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public bool AddProxyOnly4Ray { get; set; }
|
||||
@@ -29,19 +31,18 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
public bool AddProxyOnly4Singbox { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Ray { get; set; }
|
||||
public string ProxyDetour4Ray { get; set; } = string.Empty;
|
||||
|
||||
[Reactive]
|
||||
public string ProxyDetour4Singbox { get; set; }
|
||||
public string ProxyDetour4Singbox { get; set; } = string.Empty;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
#endregion Reactive
|
||||
|
||||
public FullConfigTemplateViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public FullConfigTemplateViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveSettingAsync();
|
||||
@@ -84,7 +85,7 @@ public class FullConfigTemplateViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_ = _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task<bool> SaveXrayConfigAsync()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class GlobalHotkeySettingViewModel : MyReactiveObject
|
||||
public class GlobalHotkeySettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
private readonly List<KeyEventItem> _globalHotkeys;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public GlobalHotkeySettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public GlobalHotkeySettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
_globalHotkeys = JsonUtils.DeepCopy(_config.GlobalHotkeys);
|
||||
|
||||
@@ -51,7 +52,7 @@ public class GlobalHotkeySettingViewModel : MyReactiveObject
|
||||
|
||||
if (await ConfigHandler.SaveConfig(_config) == 0)
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,6 +4,21 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<Unit, byte[]?> ScanScreenInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> BrowseImageFileInteraction { get; } = new();
|
||||
public Interaction<bool?, Unit> ShowHideWindowInteraction { get; } = new();
|
||||
|
||||
public bool DesignMode { get; set; }
|
||||
|
||||
public ProfilesViewModel ProfilesViewModel { get; } = new();
|
||||
public MsgViewModel MsgViewModel { get; } = new();
|
||||
public ClashProxiesViewModel ClashProxiesViewModel { get; } = new();
|
||||
public ClashConnectionsViewModel ClashConnectionsViewModel { get; } = new();
|
||||
public CheckUpdateViewModel CheckUpdateViewModel { get; } = new();
|
||||
public BackupAndRestoreViewModel BackupAndRestoreViewModel { get; } = new();
|
||||
public StatusBarViewModel StatusBarViewModel { get; } = StatusBarViewModel.Instance;
|
||||
|
||||
#region Menu
|
||||
|
||||
//servers
|
||||
@@ -67,15 +82,17 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
[Reactive] public bool BlNewUpdate { get; set; }
|
||||
|
||||
[Reactive] public EGirdOrientation MainGirdOrientation { get; set; }
|
||||
|
||||
#endregion Menu
|
||||
|
||||
#region Init
|
||||
|
||||
public MainWindowViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
BlIsWindows = Utils.IsWindows();
|
||||
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
@@ -191,7 +208,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
});
|
||||
GlobalHotkeySettingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.GlobalHotkeySettingWindow, null) == true)
|
||||
var globalHotkeySettingViewModel = new GlobalHotkeySettingViewModel();
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(globalHotkeySettingViewModel) == true)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
}
|
||||
@@ -233,26 +251,11 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ReloadRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await Reload());
|
||||
|
||||
AppEvents.AddServerViaScanRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||
|
||||
AppEvents.AddServerViaClipboardRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||
|
||||
AppEvents.SubscriptionsUpdateRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||
|
||||
AppEvents.HasUpdateNotified
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
@@ -260,6 +263,53 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
ProfilesViewModel.RefreshServersRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshServers());
|
||||
|
||||
var vmReloadRequestedList = new List<IObservable<Unit>>
|
||||
{
|
||||
ProfilesViewModel.ReloadRequested.AsObservable(),
|
||||
StatusBarViewModel.ReloadRequested.AsObservable(),
|
||||
CheckUpdateViewModel.ReloadRequested.AsObservable(),
|
||||
};
|
||||
|
||||
foreach (var reloadRequested in vmReloadRequestedList)
|
||||
{
|
||||
reloadRequested
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await Reload());
|
||||
}
|
||||
|
||||
StatusBarViewModel.AddServerViaScanRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||
|
||||
StatusBarViewModel.AddServerViaClipboardRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||
|
||||
StatusBarViewModel.ShowHideWindowRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blShow =>
|
||||
{
|
||||
await ShowHideWindowInteraction.Handle(blShow);
|
||||
});
|
||||
|
||||
StatusBarViewModel.SetDefaultServerRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async indexId => await ProfilesViewModel.SetDefaultServer(indexId));
|
||||
|
||||
StatusBarViewModel.SubscriptionsUpdateRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||
|
||||
_ = Init();
|
||||
}
|
||||
|
||||
@@ -267,6 +317,11 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
AppManager.Instance.ShowInTaskbar = true;
|
||||
|
||||
if (DesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//await ConfigHandler.InitBuiltinRouting(_config);
|
||||
await ConfigHandler.InitBuiltinDNS(_config);
|
||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||
@@ -279,7 +334,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||
}
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
|
||||
await Reload();
|
||||
}
|
||||
@@ -304,7 +359,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
if (success)
|
||||
{
|
||||
var indexIdOld = _config.IndexId;
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
|
||||
// If indexId changed or subIndexId is empty, directly reload.
|
||||
if (indexIdOld != _config.IndexId || _config.SubIndexId.IsNullOrEmpty())
|
||||
@@ -323,7 +378,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
||||
{
|
||||
AppEvents.AdjustMainLvColWidthRequested.Publish();
|
||||
await ProfilesViewModel.AdjustMainLvColWidth();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,14 +399,20 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task RefreshServers()
|
||||
{
|
||||
AppEvents.ProfilesRefreshRequested.Publish();
|
||||
await ProfilesViewModel.RefreshServersBiz();
|
||||
await StatusBarViewModel.RefreshServersBiz();
|
||||
|
||||
await Task.Delay(200);
|
||||
// await Task.Delay(200);
|
||||
}
|
||||
|
||||
private void RefreshSubscriptions()
|
||||
private async Task RefreshServersDispatcherAsync()
|
||||
{
|
||||
AppEvents.SubscriptionsRefreshRequested.Publish();
|
||||
await Observable.Start(async () => await RefreshServers(), RxSchedulers.MainThreadScheduler);
|
||||
}
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
{
|
||||
await Observable.Start(async () => await ProfilesViewModel.RefreshSubscriptions(), RxSchedulers.MainThreadScheduler);
|
||||
}
|
||||
|
||||
#endregion Servers && Groups
|
||||
@@ -370,19 +431,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
bool? ret = false;
|
||||
if (eConfigType == EConfigType.Custom)
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
|
||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||
}
|
||||
else if (eConfigType.IsGroupType())
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
|
||||
var addGroupServerViewModel = new AddGroupServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addGroupServerViewModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
|
||||
var addServerViewModel = new AddServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServerViewModel);
|
||||
}
|
||||
if (ret == true)
|
||||
{
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
if (item.IndexId == _config.IndexId)
|
||||
{
|
||||
await Reload();
|
||||
@@ -392,16 +456,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaClipboardAsync(string? clipboardData)
|
||||
{
|
||||
var stringData = clipboardData;
|
||||
if (clipboardData == null)
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.AddServerViaClipboard, null);
|
||||
return;
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
stringData = result;
|
||||
}
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, clipboardData, _config.SubIndexId, false);
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, stringData, _config.SubIndexId, false);
|
||||
if (ret > 0)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServersDispatcherAsync();
|
||||
NoticeManager.Instance.Enqueue(string.Format(ResUI.SuccessfullyImportedServerViaClipboard, ret));
|
||||
}
|
||||
else
|
||||
@@ -412,8 +482,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaScanAsync()
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.ScanScreenTask, null);
|
||||
await Task.CompletedTask;
|
||||
var result = await ScanScreenInteraction.Handle(Unit.Default);
|
||||
await ScanScreenResult(result);
|
||||
}
|
||||
|
||||
public async Task ScanScreenResult(byte[]? bytes)
|
||||
@@ -424,8 +494,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
public async Task AddServerViaImageAsync()
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.ScanImageTask, null);
|
||||
await Task.CompletedTask;
|
||||
var imageFileName = await BrowseImageFileInteraction.Handle(Unit.Default);
|
||||
await AddScanResultAsync(imageFileName);
|
||||
}
|
||||
|
||||
public async Task ScanImageResult(string fileName)
|
||||
@@ -450,8 +520,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
var ret = await ConfigHandler.AddBatchServers(_config, result, _config.SubIndexId, false);
|
||||
if (ret > 0)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshServers();
|
||||
await RefreshSubscriptions();
|
||||
await RefreshServersDispatcherAsync();
|
||||
NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
|
||||
}
|
||||
else
|
||||
@@ -467,9 +537,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task SubSettingAsync()
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.SubSettingWindow, null) == true)
|
||||
var subSettingViewModel = new SubSettingViewModel();
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subSettingViewModel) == true)
|
||||
{
|
||||
RefreshSubscriptions();
|
||||
await RefreshSubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,28 +555,38 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task OptionSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
|
||||
var settingViewModel = new OptionSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(settingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
AppEvents.InboundDisplayRequested.Publish();
|
||||
MainGirdOrientation = _config.UiItem.MainGirdOrientation;
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.InboundDisplayStatus();
|
||||
});
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RoutingSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.RoutingSettingWindow, null);
|
||||
var routingSettingViewModel = new RoutingSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(routingSettingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await ConfigHandler.InitBuiltinRouting(_config);
|
||||
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.RefreshRoutingsMenu();
|
||||
});
|
||||
await Reload();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DNSSettingAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.DNSSettingWindow, null);
|
||||
var dnsSettingViewModel = new DNSSettingViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(dnsSettingViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
@@ -514,7 +595,8 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
|
||||
private async Task FullConfigTemplateAsync()
|
||||
{
|
||||
var ret = await _updateView?.Invoke(EViewAction.FullConfigTemplateWindow, null);
|
||||
var fullConfigTemplateViewModel = new FullConfigTemplateViewModel();
|
||||
var ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(fullConfigTemplateViewModel);
|
||||
if (ret == true)
|
||||
{
|
||||
await Reload();
|
||||
@@ -524,7 +606,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
private async Task ClearServerStatistics()
|
||||
{
|
||||
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||
await RefreshServers();
|
||||
await RefreshServersDispatcherAsync();
|
||||
}
|
||||
|
||||
private async Task OpenTheFileLocation()
|
||||
@@ -561,6 +643,12 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
if (DesignMode)
|
||||
{
|
||||
_reloadSemaphore.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetReloadEnabled(false);
|
||||
@@ -583,12 +671,22 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
await SysProxyHandler.UpdateSysProxy(_config, false);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
AppEvents.TestServerRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.TestServerAvailability();
|
||||
});
|
||||
|
||||
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
|
||||
if (showClashUI)
|
||||
{
|
||||
AppEvents.ProxiesReloadRequested.Publish();
|
||||
//await Observable.Start(async () =>
|
||||
//{
|
||||
// await ClashProxiesViewModel.ProxiesReload();
|
||||
//}, RxSchedulers.MainThreadScheduler);
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await ClashProxiesViewModel.ProxiesReload();
|
||||
});
|
||||
}
|
||||
|
||||
ReloadResult(showClashUI);
|
||||
@@ -633,7 +731,10 @@ public class MainWindowViewModel : MyReactiveObject
|
||||
{
|
||||
await ConfigHandler.ApplyRegionalPreset(_config, type);
|
||||
await ConfigHandler.InitRouting(_config);
|
||||
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||
RxSchedulers.MainThreadScheduler.Schedule(async () =>
|
||||
{
|
||||
await StatusBarViewModel.RefreshRoutingsMenu();
|
||||
});
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
await new UpdateService(_config, UpdateTaskHandler).UpdateGeoFileAll();
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class MsgViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, Unit> DispatcherShowMsgInteraction { get; } = new();
|
||||
|
||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||
private volatile bool _lastMsgFilterNotAvailable;
|
||||
private int _showLock = 0; // 0 = unlocked, 1 = locked
|
||||
@@ -13,10 +15,9 @@ public class MsgViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoRefresh { get; set; }
|
||||
|
||||
public MsgViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public MsgViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
|
||||
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
|
||||
|
||||
@@ -64,7 +65,7 @@ public class MsgViewModel : MyReactiveObject
|
||||
sb.Append(line);
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString());
|
||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class OptionSettingViewModel : MyReactiveObject
|
||||
public class OptionSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
#region Core
|
||||
|
||||
[Reactive] public int LocalPort { get; set; }
|
||||
@@ -27,24 +29,12 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public bool EnableFragment { get; set; }
|
||||
[Reactive] public bool EnableFinalFragment { get; set; }
|
||||
[Reactive] public string FragmentPackets { get; set; }
|
||||
[Reactive] public string FragmentLength { get; set; }
|
||||
[Reactive] public string FragmentInterval { get; set; }
|
||||
[Reactive] public string FragmentLengths { get; set; }
|
||||
[Reactive] public string FragmentDelays { get; set; }
|
||||
[Reactive] public string FragmentMaxSplit { get; set; }
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region Core KCP
|
||||
|
||||
//[Reactive] public int Kcpmtu { get; set; }
|
||||
//[Reactive] public int Kcptti { get; set; }
|
||||
//[Reactive] public int KcpuplinkCapacity { get; set; }
|
||||
//[Reactive] public int KcpdownlinkCapacity { get; set; }
|
||||
//[Reactive] public int KcpreadBufferSize { get; set; }
|
||||
//[Reactive] public int KcpwriteBufferSize { get; set; }
|
||||
//[Reactive] public bool Kcpcongestion { get; set; }
|
||||
|
||||
#endregion Core KCP
|
||||
|
||||
#region UI
|
||||
|
||||
[Reactive] public bool AutoRun { get; set; }
|
||||
@@ -105,6 +95,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
[Reactive] public string TunIcmpRouting { get; set; }
|
||||
[Reactive] public bool TunEnableLegacyProtect { get; set; }
|
||||
[Reactive] public string TunRouteExcludeAddress { get; set; }
|
||||
[Reactive] public string TunIpv4Address { get; set; }
|
||||
[Reactive] public string TunIpv6Address { get; set; }
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
@@ -123,10 +115,9 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public OptionSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public OptionSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
BlIsWindows = Utils.IsWindows();
|
||||
BlIsLinux = Utils.IsLinux();
|
||||
BlIsIsMacOS = Utils.IsMacOS();
|
||||
@@ -142,8 +133,6 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.InitSettingFont, null);
|
||||
|
||||
#region Core
|
||||
|
||||
var inbound = _config.Inbound.First();
|
||||
@@ -151,6 +140,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
SecondLocalPortEnabled = inbound.SecondLocalPortEnabled;
|
||||
UdpEnabled = inbound.UdpEnabled;
|
||||
SniffingEnabled = inbound.SniffingEnabled;
|
||||
DestOverride = inbound.DestOverride ?? [];
|
||||
RouteOnly = inbound.RouteOnly;
|
||||
AllowLANConn = inbound.AllowLANConn;
|
||||
NewPort4LAN = inbound.NewPort4LAN;
|
||||
@@ -169,24 +159,12 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
EnableFragment = _config.CoreBasicItem.EnableFragment;
|
||||
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
|
||||
FragmentPackets = _config.Fragment4RayItem?.Packets;
|
||||
FragmentLength = _config.Fragment4RayItem?.Length;
|
||||
FragmentInterval = _config.Fragment4RayItem?.Interval;
|
||||
FragmentLengths = Utils.List2String(_config.Fragment4RayItem?.Lengths);
|
||||
FragmentDelays = Utils.List2String(_config.Fragment4RayItem?.Delays);
|
||||
FragmentMaxSplit = _config.Fragment4RayItem?.MaxSplit;
|
||||
|
||||
#endregion Core
|
||||
|
||||
#region Core KCP
|
||||
|
||||
//Kcpmtu = _config.kcpItem.mtu;
|
||||
//Kcptti = _config.kcpItem.tti;
|
||||
//KcpuplinkCapacity = _config.kcpItem.uplinkCapacity;
|
||||
//KcpdownlinkCapacity = _config.kcpItem.downlinkCapacity;
|
||||
//KcpreadBufferSize = _config.kcpItem.readBufferSize;
|
||||
//KcpwriteBufferSize = _config.kcpItem.writeBufferSize;
|
||||
//Kcpcongestion = _config.kcpItem.congestion;
|
||||
|
||||
#endregion Core KCP
|
||||
|
||||
#region UI
|
||||
|
||||
AutoRun = _config.GuiItem.AutoRun;
|
||||
@@ -238,6 +216,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
TunIcmpRouting = _config.TunModeItem.IcmpRouting;
|
||||
TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect;
|
||||
TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true);
|
||||
TunIpv4Address = _config.TunModeItem.Ipv4Address;
|
||||
TunIpv6Address = _config.TunModeItem.Ipv6Address;
|
||||
|
||||
#endregion Tun mode
|
||||
|
||||
@@ -310,35 +290,33 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort);
|
||||
return;
|
||||
}
|
||||
var fragmentLengths = Utils.String2List(FragmentLengths) ?? [];
|
||||
var fragmentDelays = Utils.String2List(FragmentDelays) ?? [];
|
||||
if (fragmentLengths.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|
||||
|| fragmentDelays.Any(item => !Utils.TryParseRange(item, 0, int.MaxValue, out _, out _))
|
||||
|| (FragmentMaxSplit.IsNotEmpty() && !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _)))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
var needReboot = EnableStatistics != _config.GuiItem.EnableStatistics
|
||||
|| DisplayRealTimeSpeed != _config.GuiItem.DisplayRealTimeSpeed
|
||||
|| EnableDragDropSort != _config.UiItem.EnableDragDropSort
|
||||
|| EnableHWA != _config.GuiItem.EnableHWA
|
||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily
|
||||
|| MainGirdOrientation != (int)_config.UiItem.MainGirdOrientation;
|
||||
|
||||
//if (Utile.IsNullOrEmpty(Kcpmtu.ToString()) || !Utile.IsNumeric(Kcpmtu.ToString())
|
||||
// || Utile.IsNullOrEmpty(Kcptti.ToString()) || !Utile.IsNumeric(Kcptti.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpuplinkCapacity.ToString()) || !Utile.IsNumeric(KcpuplinkCapacity.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpdownlinkCapacity.ToString()) || !Utile.IsNumeric(KcpdownlinkCapacity.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpreadBufferSize.ToString()) || !Utile.IsNumeric(KcpreadBufferSize.ToString())
|
||||
// || Utile.IsNullOrEmpty(KcpwriteBufferSize.ToString()) || !Utile.IsNumeric(KcpwriteBufferSize.ToString()))
|
||||
//{
|
||||
// NoticeHandler.Instance.Enqueue(ResUI.FillKcpParameters);
|
||||
// return;
|
||||
//}
|
||||
|| CurrentFontFamily != _config.UiItem.CurrentFontFamily;
|
||||
|
||||
//Core
|
||||
_config.Inbound.First().LocalPort = LocalPort;
|
||||
_config.Inbound.First().SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||
_config.Inbound.First().UdpEnabled = UdpEnabled;
|
||||
_config.Inbound.First().SniffingEnabled = SniffingEnabled;
|
||||
_config.Inbound.First().DestOverride = DestOverride?.ToList();
|
||||
_config.Inbound.First().RouteOnly = RouteOnly;
|
||||
_config.Inbound.First().AllowLANConn = AllowLANConn;
|
||||
_config.Inbound.First().NewPort4LAN = NewPort4LAN;
|
||||
_config.Inbound.First().User = User;
|
||||
_config.Inbound.First().Pass = Pass;
|
||||
var inbound = _config.Inbound.First();
|
||||
inbound.LocalPort = LocalPort;
|
||||
inbound.SecondLocalPortEnabled = SecondLocalPortEnabled;
|
||||
inbound.UdpEnabled = UdpEnabled;
|
||||
inbound.SniffingEnabled = SniffingEnabled;
|
||||
inbound.DestOverride = DestOverride?.ToList();
|
||||
inbound.RouteOnly = RouteOnly;
|
||||
inbound.AllowLANConn = AllowLANConn;
|
||||
inbound.NewPort4LAN = NewPort4LAN;
|
||||
inbound.User = User;
|
||||
inbound.Pass = Pass;
|
||||
if (_config.Inbound.Count > 1)
|
||||
{
|
||||
_config.Inbound.RemoveAt(1);
|
||||
@@ -353,30 +331,12 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.CoreBasicItem.EnableCacheFile4Sbox = EnableCacheFile4Sbox;
|
||||
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
|
||||
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
|
||||
if (EnableFragment)
|
||||
{
|
||||
if (!Utils.TryParseRange(FragmentLength, 0, int.MaxValue, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
if (!Utils.TryParseRange(FragmentInterval, 1, 100, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
if (FragmentMaxSplit.IsNotEmpty()
|
||||
&& !Utils.TryParseMaxSplit(FragmentMaxSplit, 0, 10000, out _, out _))
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.FillFragmentParameterError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_config.CoreBasicItem.EnableFragment = EnableFragment;
|
||||
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
|
||||
_config.Fragment4RayItem ??= new();
|
||||
_config.Fragment4RayItem.Packets = FragmentPackets;
|
||||
_config.Fragment4RayItem.Length = FragmentLength;
|
||||
_config.Fragment4RayItem.Interval = FragmentInterval;
|
||||
_config.Fragment4RayItem.Lengths = fragmentLengths;
|
||||
_config.Fragment4RayItem.Delays = fragmentDelays;
|
||||
_config.Fragment4RayItem.MaxSplit = FragmentMaxSplit;
|
||||
|
||||
_config.GuiItem.AutoRun = AutoRun;
|
||||
@@ -422,6 +382,8 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
_config.TunModeItem.IcmpRouting = TunIcmpRouting;
|
||||
_config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect;
|
||||
_config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress);
|
||||
_config.TunModeItem.Ipv4Address = TunIpv4Address;
|
||||
_config.TunModeItem.Ipv6Address = TunIpv6Address;
|
||||
|
||||
//coreType
|
||||
await SaveCoreType();
|
||||
@@ -432,7 +394,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||
AppManager.Instance.Reset();
|
||||
|
||||
NoticeManager.Instance.Enqueue(needReboot ? ResUI.NeedRebootTips : ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ProfilesSelectViewModel : MyReactiveObject
|
||||
public class ProfilesSelectViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
||||
|
||||
#region private prop
|
||||
|
||||
private string _serverFilter = string.Empty;
|
||||
@@ -12,6 +16,8 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
|
||||
#endregion private prop
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||
@@ -36,18 +42,25 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool FilterExclude { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public bool MultiSelect { get; set; }
|
||||
|
||||
#endregion ObservableCollection
|
||||
|
||||
#region Init
|
||||
|
||||
public ProfilesSelectViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ProfilesSelectViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
_subIndexId = _config.SubIndexId ?? string.Empty;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
SaveCmd = ReactiveCommand.Create(() =>
|
||||
{
|
||||
SelectFinish();
|
||||
});
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.SelectedSub,
|
||||
y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id)
|
||||
@@ -107,7 +120,7 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -125,7 +138,13 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
|
||||
await RefreshServers();
|
||||
|
||||
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||
try
|
||||
{
|
||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ServerFilterChanged(bool c)
|
||||
@@ -157,8 +176,6 @@ public class ProfilesSelectViewModel : MyReactiveObject
|
||||
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||
SelectedProfile = selected ?? lstModel.First();
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||
}
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
|
||||
@@ -2,6 +2,17 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
public Interaction<ProfileItem, bool> SaveFileDialogInteraction { get; } = new();
|
||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> ProfilesFocusInteraction { get; } = new();
|
||||
public Interaction<string, Unit> ShareServerInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> DispatcherRefreshServersBizInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> AdjustMainLvColWidthInteraction { get; } = new();
|
||||
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
public EventChannel<Unit> RefreshServersRequested { get; } = new();
|
||||
|
||||
#region private prop
|
||||
|
||||
private List<ProfileItem> _lstProfile;
|
||||
@@ -82,10 +93,9 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
#region Init
|
||||
|
||||
public ProfilesViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public ProfilesViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
#region WhenAnyValue && ReactiveCommand
|
||||
|
||||
@@ -236,26 +246,11 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
#region AppEvents
|
||||
|
||||
AppEvents.ProfilesRefreshRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshServersBiz());
|
||||
|
||||
AppEvents.SubscriptionsRefreshRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshSubscriptions());
|
||||
|
||||
AppEvents.DispatcherStatisticsRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async result => await UpdateStatistics(result));
|
||||
|
||||
AppEvents.SetDefaultServerRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async indexId => await SetDefaultServer(indexId));
|
||||
|
||||
#endregion AppEvents
|
||||
|
||||
_ = Init();
|
||||
@@ -277,7 +272,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
AppEvents.ReloadRequested.Publish();
|
||||
ReloadRequested.Publish();
|
||||
}
|
||||
|
||||
public async Task SetSpeedTestResult(SpeedTestResult result)
|
||||
@@ -350,7 +345,13 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
await RefreshServers();
|
||||
|
||||
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||
try
|
||||
{
|
||||
await ProfilesFocusInteraction.Handle(Unit.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ServerFilterChanged(bool c)
|
||||
@@ -368,19 +369,21 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
public async Task RefreshServers()
|
||||
{
|
||||
AppEvents.ProfilesRefreshRequested.Publish();
|
||||
RefreshServersRequested.Publish();
|
||||
|
||||
await Task.Delay(200);
|
||||
// await Task.Delay(200);
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RefreshServersBiz()
|
||||
public async Task RefreshServersBiz()
|
||||
{
|
||||
var lstModel = await GetProfileItemsEx(_config.SubIndexId, _serverFilter);
|
||||
_lstProfile = JsonUtils.Deserialize<List<ProfileItem>>(JsonUtils.Serialize(lstModel)) ?? [];
|
||||
|
||||
ProfileItems.Clear();
|
||||
ProfileItems.AddRange(lstModel);
|
||||
if (lstModel.Count > 0)
|
||||
ProfileItems.AddRange(lstModel ?? []);
|
||||
if (lstModel?.Count > 0)
|
||||
{
|
||||
ProfileItemModel? selected = null;
|
||||
if (!_pendingSelectIndexId.IsNullOrEmpty())
|
||||
@@ -392,10 +395,16 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
SelectedProfile = selected ?? lstModel.First();
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||
try
|
||||
{
|
||||
await DispatcherRefreshServersBizInteraction.Handle(Unit.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshSubscriptions()
|
||||
public async Task RefreshSubscriptions()
|
||||
{
|
||||
var subItems = await AppManager.Instance.SubItems();
|
||||
subItems.Insert(0, new SubItem { Remarks = ResUI.AllGroupServers });
|
||||
@@ -408,6 +417,11 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
: null) ?? subItems.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task AdjustMainLvColWidth()
|
||||
{
|
||||
await AdjustMainLvColWidthInteraction.Handle(Unit.Default);
|
||||
}
|
||||
|
||||
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, filter);
|
||||
@@ -491,15 +505,18 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
bool? ret = false;
|
||||
if (eConfigType == EConfigType.Custom)
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServer2Window, item);
|
||||
var addServer2ViewModel = new AddServer2ViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServer2ViewModel);
|
||||
}
|
||||
else if (eConfigType.IsGroupType())
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddGroupServerWindow, item);
|
||||
var addGroupServerViewModel = new AddGroupServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addGroupServerViewModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = await _updateView?.Invoke(EViewAction.AddServerWindow, item);
|
||||
var addServerViewModel = new AddServerViewModel(item);
|
||||
ret = await AppManager.Instance.WindowDialog.ShowDialogAsync(addServerViewModel);
|
||||
}
|
||||
if (ret == true)
|
||||
{
|
||||
@@ -518,7 +535,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -539,7 +556,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
private async Task RemoveDuplicateServer()
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -576,7 +593,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
await SetDefaultServer(SelectedProfile.IndexId);
|
||||
}
|
||||
|
||||
private async Task SetDefaultServer(string? indexId)
|
||||
public async Task SetDefaultServer(string? indexId)
|
||||
{
|
||||
if (indexId.IsNullOrEmpty())
|
||||
{
|
||||
@@ -614,7 +631,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
await _updateView?.Invoke(EViewAction.ShareServer, url);
|
||||
await ShareServerInteraction.Handle(url);
|
||||
}
|
||||
|
||||
private async Task GenGroupAllServer()
|
||||
@@ -783,13 +800,13 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
}
|
||||
else
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, result.Data);
|
||||
await SetClipboardDataInteraction.Handle((string)result.Data);
|
||||
NoticeManager.Instance.SendMessage(ResUI.OperationSuccess);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.SaveFileDialog, item);
|
||||
await SaveFileDialogInteraction.Handle(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,11 +855,11 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
{
|
||||
if (blEncode)
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, Utils.Base64Encode(sb.ToString()));
|
||||
await SetClipboardDataInteraction.Handle(Utils.Base64Encode(sb.ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
|
||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
||||
}
|
||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||
}
|
||||
@@ -865,7 +882,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
|
||||
if (!result.IsNullOrEmpty())
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, result);
|
||||
await SetClipboardDataInteraction.Handle(result);
|
||||
NoticeManager.Instance.SendMessage(ResUI.BatchExportURLSuccessfully);
|
||||
}
|
||||
else
|
||||
@@ -893,7 +910,8 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true)
|
||||
var subEditViewModel = new SubEditViewModel(item);
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subEditViewModel) == true)
|
||||
{
|
||||
await RefreshSubscriptions();
|
||||
await SubSelectedChangedAsync(true);
|
||||
@@ -908,7 +926,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class RoutingRuleDetailsViewModel : MyReactiveObject
|
||||
public class RoutingRuleDetailsViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public IList<string> ProtocolItems { get; set; }
|
||||
public IList<string> InboundTagItems { get; set; }
|
||||
|
||||
@@ -23,13 +25,17 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
||||
[Reactive]
|
||||
public bool AutoSort { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SelectProfileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public RoutingRuleDetailsViewModel(RulesItem rulesItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public RoutingRuleDetailsViewModel(RulesItem rulesItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SelectProfileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SelectProfileAsync();
|
||||
});
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveRulesAsync();
|
||||
@@ -88,6 +94,24 @@ public class RoutingRuleDetailsViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
//NoticeHandler.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
await _updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task SelectProfileAsync()
|
||||
{
|
||||
var profileSelectViewModel = new ProfilesSelectViewModel();
|
||||
profileSelectViewModel.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
var result = await AppManager.Instance.WindowDialog.ShowDialogAsync(profileSelectViewModel);
|
||||
if (result != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var profileItem = await profileSelectViewModel.GetProfileItem();
|
||||
if (profileItem != null)
|
||||
{
|
||||
SelectedSource.OutboundTag = profileItem.Remarks;
|
||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
public class RoutingRuleSettingViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> ReadTextFromClipboardInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> BrowseRulesFileInteraction { get; } = new();
|
||||
|
||||
private List<RulesItem> _rules;
|
||||
|
||||
[Reactive]
|
||||
@@ -27,10 +34,9 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public RoutingRuleSettingViewModel(RoutingItem routingItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public RoutingRuleSettingViewModel(RoutingItem routingItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedSource,
|
||||
@@ -42,7 +48,8 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
});
|
||||
ImportRulesFromFileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.ImportRulesFromFile, null);
|
||||
var fileName = await BrowseRulesFileInteraction.Handle(Unit.Default);
|
||||
await ImportRulesFromFileAsync(fileName);
|
||||
});
|
||||
ImportRulesFromClipboardCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
@@ -131,7 +138,8 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.RoutingRuleDetailsWindow, item) == true)
|
||||
var routingRuleDetailsViewModel = new RoutingRuleDetailsViewModel(item);
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(routingRuleDetailsViewModel) == true)
|
||||
{
|
||||
if (blNew)
|
||||
{
|
||||
@@ -148,7 +156,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -191,7 +199,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, JsonUtils.Serialize(lst, options));
|
||||
await SetClipboardDataInteraction.Handle(JsonUtils.Serialize(lst, options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +242,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.SaveRoutingItem(_config, item) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -266,12 +274,18 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
|
||||
public async Task ImportRulesFromClipboardAsync(string? clipboardData)
|
||||
{
|
||||
var stringData = clipboardData;
|
||||
if (clipboardData == null)
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.ImportRulesFromClipboard, null);
|
||||
return;
|
||||
var result = await ReadTextFromClipboardInteraction.Handle(Unit.Default);
|
||||
if (result.IsNullOrEmpty())
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
return;
|
||||
}
|
||||
stringData = result;
|
||||
}
|
||||
var ret = await AddBatchRoutingRulesAsync(SelectedRouting, clipboardData);
|
||||
var ret = await AddBatchRoutingRulesAsync(SelectedRouting, stringData);
|
||||
if (ret == 0)
|
||||
{
|
||||
RefreshRulesItems();
|
||||
@@ -301,7 +315,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
||||
private async Task<int> AddBatchRoutingRulesAsync(RoutingItem routingItem, string? clipboardData)
|
||||
{
|
||||
var blReplace = false;
|
||||
if (await _updateView?.Invoke(EViewAction.AddBatchRoutingRulesYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.AddBatchRoutingRulesYesNo) == false)
|
||||
{
|
||||
blReplace = true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class RoutingSettingViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
|
||||
#region Reactive
|
||||
|
||||
public IObservableCollection<RoutingItemModel> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItemModel>();
|
||||
@@ -26,10 +28,9 @@ public class RoutingSettingViewModel : MyReactiveObject
|
||||
|
||||
#endregion Reactive
|
||||
|
||||
public RoutingSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public RoutingSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedSource,
|
||||
@@ -131,7 +132,8 @@ public class RoutingSettingViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.RoutingRuleSettingWindow, item) == true)
|
||||
var routingRuleSettingViewModel = new RoutingRuleSettingViewModel(item);
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(routingRuleSettingViewModel) == true)
|
||||
{
|
||||
await RefreshRoutingItems();
|
||||
IsModified = true;
|
||||
@@ -145,7 +147,7 @@ public class RoutingSettingViewModel : MyReactiveObject
|
||||
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectRules);
|
||||
return;
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,20 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class StatusBarViewModel : MyReactiveObject
|
||||
{
|
||||
private static readonly Lazy<StatusBarViewModel> _instance = new(() => new(null));
|
||||
public Interaction<string, Unit> SetClipboardDataInteraction { get; } = new();
|
||||
public Interaction<Unit, string?> PasswordInputInteraction { get; } = new();
|
||||
public Interaction<Unit, Unit> DispatcherRefreshIconInteraction { get; } = new();
|
||||
public EventChannel<bool> SubscriptionsUpdateRequested { get; } = new();
|
||||
public EventChannel<bool?> ShowHideWindowRequested { get; } = new();
|
||||
|
||||
private static readonly Lazy<StatusBarViewModel> _instance = new(() => new());
|
||||
public static StatusBarViewModel Instance => _instance.Value;
|
||||
|
||||
public EventChannel<string> SetDefaultServerRequested { get; } = new();
|
||||
public EventChannel<Unit> ReloadRequested { get; } = new();
|
||||
public EventChannel<Unit> AddServerViaScanRequested { get; } = new();
|
||||
public EventChannel<Unit> AddServerViaClipboardRequested { get; } = new();
|
||||
|
||||
#region ObservableCollection
|
||||
|
||||
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
|
||||
@@ -92,12 +103,12 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
#endregion UI
|
||||
|
||||
public StatusBarViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public StatusBarViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
SelectedRouting = new();
|
||||
SelectedServer = new();
|
||||
RunningServerToolTipText = "-";
|
||||
RunningServerToolTipText = GetRunningServerToolTipText("-");
|
||||
BlSystemProxyPacVisible = Utils.IsWindows();
|
||||
BlIsNonWindows = Utils.IsNonWindows();
|
||||
|
||||
@@ -140,17 +151,17 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
AppEvents.ShowHideWindowRequested.Publish(null);
|
||||
ShowHideWindowRequested.Publish(null);
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
ShowWindowCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
AppEvents.ShowHideWindowRequested.Publish(true);
|
||||
ShowHideWindowRequested.Publish(true);
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
HideWindowCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
AppEvents.ShowHideWindowRequested.Publish(false);
|
||||
ShowHideWindowRequested.Publish(false);
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
|
||||
@@ -193,31 +204,11 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
#region AppEvents
|
||||
|
||||
if (updateView != null)
|
||||
{
|
||||
InitUpdateView(updateView);
|
||||
}
|
||||
|
||||
AppEvents.DispatcherStatisticsRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async result => await UpdateStatistics(result));
|
||||
|
||||
AppEvents.RoutingsMenuRefreshRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshRoutingsMenu());
|
||||
|
||||
AppEvents.TestServerRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await TestServerAvailability());
|
||||
|
||||
AppEvents.InboundDisplayRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await InboundDisplayStatus());
|
||||
|
||||
AppEvents.SysProxyChangeRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
@@ -238,18 +229,6 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
BlRouting = true;
|
||||
}
|
||||
|
||||
public void InitUpdateView(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
{
|
||||
_updateView = updateView;
|
||||
if (_updateView != null)
|
||||
{
|
||||
AppEvents.ProfilesRefreshRequested
|
||||
.AsObservable()
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(async _ => await RefreshServersBiz()); //.DisposeWith(_disposables);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyProxyCmdToClipboard()
|
||||
{
|
||||
var cmd = Utils.IsWindows() ? "set" : "export";
|
||||
@@ -264,28 +243,28 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
sb.AppendLine($"{cmd} HTTPS_PROXY={Global.HttpProtocol}{address}");
|
||||
sb.AppendLine($"{cmd} ALL_PROXY={Global.Socks5Protocol}{address}");
|
||||
|
||||
await _updateView?.Invoke(EViewAction.SetClipboardData, sb.ToString());
|
||||
await SetClipboardDataInteraction.Handle(sb.ToString());
|
||||
}
|
||||
|
||||
private async Task AddServerViaClipboard()
|
||||
{
|
||||
AppEvents.AddServerViaClipboardRequested.Publish();
|
||||
AddServerViaClipboardRequested.Publish();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
private async Task AddServerViaScan()
|
||||
{
|
||||
AppEvents.AddServerViaScanRequested.Publish();
|
||||
AddServerViaScanRequested.Publish();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
private async Task UpdateSubscriptionProcess(bool blProxy)
|
||||
{
|
||||
AppEvents.SubscriptionsUpdateRequested.Publish(blProxy);
|
||||
SubscriptionsUpdateRequested.Publish(blProxy);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
private async Task RefreshServersBiz()
|
||||
public async Task RefreshServersBiz()
|
||||
{
|
||||
await RefreshServersMenu();
|
||||
|
||||
@@ -293,22 +272,26 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
var running = await ConfigHandler.GetDefaultServer(_config);
|
||||
if (running != null)
|
||||
{
|
||||
RunningServerDisplay =
|
||||
RunningServerToolTipText = running.GetSummary();
|
||||
RunningServerDisplay = running.GetSummary();
|
||||
RunningServerToolTipText = GetRunningServerToolTipText(RunningServerDisplay);
|
||||
}
|
||||
else
|
||||
{
|
||||
RunningServerDisplay =
|
||||
RunningServerToolTipText = ResUI.CheckServerSettings;
|
||||
RunningServerDisplay = ResUI.CheckServerSettings;
|
||||
RunningServerToolTipText = GetRunningServerToolTipText(RunningServerDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetRunningServerToolTipText(string serverInfo)
|
||||
{
|
||||
return Utils.IsLinux() ? Global.AppName : serverInfo;
|
||||
}
|
||||
|
||||
private async Task RefreshServersMenu()
|
||||
{
|
||||
var lstModel = await AppManager.Instance.ProfileModels(_config.SubIndexId, "");
|
||||
|
||||
Servers.Clear();
|
||||
if (lstModel.Count > _config.GuiItem.TrayMenuServersLimit)
|
||||
if (lstModel?.Count > _config.GuiItem.TrayMenuServersLimit)
|
||||
{
|
||||
BlServers = false;
|
||||
return;
|
||||
@@ -327,6 +310,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
SelectedServer = item;
|
||||
}
|
||||
}
|
||||
Servers.Clear();
|
||||
Servers.AddRange(models);
|
||||
}
|
||||
|
||||
@@ -344,7 +328,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
AppEvents.SetDefaultServerRequested.Publish(SelectedServer.ID);
|
||||
SetDefaultServerRequested.Publish(SelectedServer.ID);
|
||||
}
|
||||
|
||||
public async Task TestServerAvailability()
|
||||
@@ -406,11 +390,18 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
if (blChange)
|
||||
{
|
||||
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
||||
try
|
||||
{
|
||||
await DispatcherRefreshIconInteraction.Handle(Unit.Default);
|
||||
}
|
||||
catch (UnhandledInteractionException<Unit, Unit>)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshRoutingsMenu()
|
||||
public async Task RefreshRoutingsMenu()
|
||||
{
|
||||
var routings = await AppManager.Instance.RoutingItems();
|
||||
|
||||
@@ -441,8 +432,8 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
|
||||
{
|
||||
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||
AppEvents.ReloadRequested.Publish();
|
||||
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
||||
ReloadRequested.Publish();
|
||||
await DispatcherRefreshIconInteraction.Handle(Unit.Default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,8 +470,8 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
}
|
||||
else
|
||||
{
|
||||
bool? passwordResult = await _updateView?.Invoke(EViewAction.PasswordInput, null);
|
||||
if (passwordResult == false)
|
||||
var password = await PasswordInputInteraction.Handle(Unit.Default);
|
||||
if (password.IsNullOrEmpty())
|
||||
{
|
||||
_config.TunModeItem.EnableTun = false;
|
||||
return;
|
||||
@@ -489,7 +480,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
}
|
||||
|
||||
await ConfigHandler.SaveConfig(_config);
|
||||
AppEvents.ReloadRequested.Publish();
|
||||
ReloadRequested.Publish();
|
||||
}
|
||||
|
||||
private bool AllowEnableTun()
|
||||
@@ -513,7 +504,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||
|
||||
#region UI
|
||||
|
||||
private async Task InboundDisplayStatus()
|
||||
public async Task InboundDisplayStatus()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
||||
|
||||
@@ -1,17 +1,38 @@
|
||||
namespace ServiceLib.ViewModels;
|
||||
|
||||
public class SubEditViewModel : MyReactiveObject
|
||||
public class SubEditViewModel : MyReactiveObject, ICloseable
|
||||
{
|
||||
public event EventHandler? RequestClose;
|
||||
|
||||
[Reactive]
|
||||
public SubItem SelectedSource { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> SelectPrevProfileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SelectNextProfileCmd { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||
|
||||
public SubEditViewModel(SubItem subItem, Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public SubEditViewModel(SubItem subItem)
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
SelectPrevProfileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var profileItem = await SelectProfileAsync();
|
||||
if (profileItem != null)
|
||||
{
|
||||
SelectedSource?.PrevProfile = profileItem.Remarks;
|
||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
||||
}
|
||||
});
|
||||
SelectNextProfileCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var profileItem = await SelectProfileAsync();
|
||||
if (profileItem != null)
|
||||
{
|
||||
SelectedSource?.NextProfile = profileItem.Remarks;
|
||||
SelectedSource = JsonUtils.DeepCopy(SelectedSource);
|
||||
}
|
||||
});
|
||||
SaveCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveSubAsync();
|
||||
@@ -49,11 +70,24 @@ public class SubEditViewModel : MyReactiveObject
|
||||
if (await ConfigHandler.AddSubItem(_config, SelectedSource) == 0)
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationSuccess);
|
||||
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
NoticeManager.Instance.Enqueue(ResUI.OperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ProfileItem?> SelectProfileAsync()
|
||||
{
|
||||
var profileSelectViewModel = new ProfilesSelectViewModel();
|
||||
profileSelectViewModel.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
var result = await AppManager.Instance.WindowDialog.ShowDialogAsync(profileSelectViewModel);
|
||||
if (result != true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var profileItem = await profileSelectViewModel.GetProfileItem();
|
||||
return profileItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public class SubSettingViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, bool> ShowYesNoInteraction { get; } = new();
|
||||
public Interaction<string, Unit> ShareSubInteraction { get; } = new();
|
||||
|
||||
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||
|
||||
[Reactive]
|
||||
@@ -15,10 +18,9 @@ public class SubSettingViewModel : MyReactiveObject
|
||||
public ReactiveCommand<Unit, Unit> SubShareCmd { get; }
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
public SubSettingViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||
public SubSettingViewModel()
|
||||
{
|
||||
_config = AppManager.Instance.Config;
|
||||
_updateView = updateView;
|
||||
|
||||
var canEditRemove = this.WhenAnyValue(
|
||||
x => x.SelectedSource,
|
||||
@@ -38,7 +40,7 @@ public class SubSettingViewModel : MyReactiveObject
|
||||
}, canEditRemove);
|
||||
SubShareCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await _updateView?.Invoke(EViewAction.ShareSub, SelectedSource?.Url);
|
||||
await ShareSubInteraction.Handle(SelectedSource?.Url);
|
||||
}, canEditRemove);
|
||||
|
||||
_ = Init();
|
||||
@@ -72,7 +74,8 @@ public class SubSettingViewModel : MyReactiveObject
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await _updateView?.Invoke(EViewAction.SubEditWindow, item) == true)
|
||||
var subEditViewModel = new SubEditViewModel(item);
|
||||
if (await AppManager.Instance.WindowDialog.ShowDialogAsync(subEditViewModel) == true)
|
||||
{
|
||||
await RefreshSubItems();
|
||||
IsModified = true;
|
||||
@@ -81,7 +84,7 @@ public class SubSettingViewModel : MyReactiveObject
|
||||
|
||||
private async Task DeleteSubAsync()
|
||||
{
|
||||
if (await _updateView?.Invoke(EViewAction.ShowYesNo, null) == false)
|
||||
if (await ShowYesNoInteraction.Handle(ResUI.RemoveServer) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
Name="v2rayN"
|
||||
x:DataType="vms:StatusBarViewModel"
|
||||
RequestedThemeVariant="Default">
|
||||
RequestedThemeVariant="Default">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -22,6 +22,14 @@
|
||||
<StyleInclude Source="Assets/GlobalStyles.axaml" />
|
||||
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
|
||||
<dialogHost:DialogHostStyles />
|
||||
|
||||
<Style Selector="dialogHost|DialogHost">
|
||||
<Setter Property="OverlayBackground" Value="{DynamicResource SemiColorOverlayBackground}" />
|
||||
<Setter Property="Background" Value="{DynamicResource SemiColorFill0}" />
|
||||
|
||||
<Setter Property="dialogHost:DialogHostStyle.BoxShadow" Value="{DynamicResource SemiShadowElevated}" />
|
||||
<Setter Property="dialogHost:DialogHostStyle.CornerRadius" Value="6" />
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
|
||||
<TrayIcon.Icons>
|
||||
|
||||
@@ -15,6 +15,9 @@ public partial class App : Application
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
var viewLocator = SimpleViewLocator.Instance;
|
||||
DataTemplates.Add(viewLocator);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (!Design.IsDesignMode)
|
||||
@@ -23,7 +26,9 @@ public partial class App : Application
|
||||
DataContext = StatusBarViewModel.Instance;
|
||||
}
|
||||
|
||||
var mainWindow = new MainWindow();
|
||||
var mainWindowViewModel = new MainWindowViewModel();
|
||||
var mainWindow = (MainWindow)viewLocator.Build(mainWindowViewModel);
|
||||
mainWindow.ViewModel = mainWindowViewModel;
|
||||
desktop.MainWindow = mainWindow;
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
|
||||
@@ -4,7 +4,6 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
|
||||
{
|
||||
public WindowBase()
|
||||
{
|
||||
Initialized += OnWindowInitialized;
|
||||
Loaded += OnLoaded;
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
@@ -20,7 +19,7 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void OnWindowInitialized(object? sender, EventArgs e)
|
||||
protected virtual void OnLoaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -30,23 +29,22 @@ public class WindowBase<TViewModel> : ReactiveWindow<TViewModel> where TViewMode
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizeItem.Width > 0 && !Width.Equals(sizeItem.Width))
|
||||
{
|
||||
Width = sizeItem.Width;
|
||||
}
|
||||
var screen = Screens.ScreenFromWindow(this) ?? Screens.Primary;
|
||||
var scaling = screen.Scaling > 0 ? screen.Scaling : 1.0;
|
||||
var workingArea = screen.WorkingArea;
|
||||
|
||||
if (sizeItem.Height > 0 && !Height.Equals(sizeItem.Height))
|
||||
{
|
||||
Height = sizeItem.Height;
|
||||
}
|
||||
var width = Math.Min(sizeItem.Width, workingArea.Width / scaling);
|
||||
var height = Math.Min(sizeItem.Height, workingArea.Height / scaling);
|
||||
var x = workingArea.X + ((workingArea.Width - (width * scaling)) / 2);
|
||||
var y = workingArea.Y + ((workingArea.Height - (height * scaling)) / 2);
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Position = new PixelPoint((int)x, (int)y);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
protected virtual void OnLoaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
|
||||
69
v2rayN/v2rayN.Desktop/Common/SimpleViewLocator.cs
Normal file
69
v2rayN/v2rayN.Desktop/Common/SimpleViewLocator.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Avalonia.Controls.Templates;
|
||||
using v2rayN.Desktop.ViewModels;
|
||||
using v2rayN.Desktop.Views;
|
||||
|
||||
namespace v2rayN.Desktop.Common;
|
||||
|
||||
public class SimpleViewLocator : IDataTemplate
|
||||
{
|
||||
private static readonly Lazy<SimpleViewLocator> _instance = new(() => new SimpleViewLocator());
|
||||
|
||||
private readonly Dictionary<Type, Func<Control?>> _locator = new();
|
||||
|
||||
private SimpleViewLocator()
|
||||
{
|
||||
RegisterViewFactory<AddGroupServerViewModel, AddGroupServerWindow>();
|
||||
RegisterViewFactory<AddServer2ViewModel, AddServer2Window>();
|
||||
RegisterViewFactory<AddServerViewModel, AddServerWindow>();
|
||||
RegisterViewFactory<BackupAndRestoreViewModel, BackupAndRestoreView>();
|
||||
RegisterViewFactory<CheckUpdateViewModel, CheckUpdateView>();
|
||||
RegisterViewFactory<ClashConnectionsViewModel, ClashConnectionsView>();
|
||||
RegisterViewFactory<ClashProxiesViewModel, ClashProxiesView>();
|
||||
RegisterViewFactory<DNSSettingViewModel, DNSSettingWindow>();
|
||||
RegisterViewFactory<FullConfigTemplateViewModel, FullConfigTemplateWindow>();
|
||||
RegisterViewFactory<GlobalHotkeySettingViewModel, GlobalHotkeySettingWindow>();
|
||||
RegisterViewFactory<MainWindowViewModel, MainWindow>();
|
||||
RegisterViewFactory<MsgViewModel, MsgView>();
|
||||
RegisterViewFactory<OptionSettingViewModel, OptionSettingWindow>();
|
||||
RegisterViewFactory<ProfilesSelectViewModel, ProfilesSelectWindow>();
|
||||
RegisterViewFactory<ProfilesViewModel, ProfilesView>();
|
||||
RegisterViewFactory<RoutingRuleDetailsViewModel, RoutingRuleDetailsWindow>();
|
||||
RegisterViewFactory<RoutingRuleSettingViewModel, RoutingRuleSettingWindow>();
|
||||
RegisterViewFactory<RoutingSettingViewModel, RoutingSettingWindow>();
|
||||
RegisterViewFactory<StatusBarViewModel, StatusBarView>();
|
||||
RegisterViewFactory<SubEditViewModel, SubEditWindow>();
|
||||
RegisterViewFactory<SubSettingViewModel, SubSettingWindow>();
|
||||
RegisterViewFactory<ThemeSettingViewModel, ThemeSettingView>();
|
||||
}
|
||||
|
||||
public static SimpleViewLocator Instance => _instance.Value;
|
||||
|
||||
public Control Build(object? data)
|
||||
{
|
||||
if (data is null)
|
||||
{
|
||||
return new TextBlock { Text = "No VM provided" };
|
||||
}
|
||||
|
||||
_locator.TryGetValue(data.GetType(), out var factory);
|
||||
|
||||
return factory?.Invoke() ?? new TextBlock { Text = $"VM Not Registered: {data.GetType()}" };
|
||||
}
|
||||
|
||||
public bool Match(object? data)
|
||||
{
|
||||
return data is MyReactiveObject;
|
||||
}
|
||||
|
||||
public void RegisterViewFactory<TViewModel>(Func<Control> factory) where TViewModel : class
|
||||
{
|
||||
_locator.Add(typeof(TViewModel), factory);
|
||||
}
|
||||
|
||||
public void RegisterViewFactory<TViewModel, TView>()
|
||||
where TViewModel : class
|
||||
where TView : Control, new()
|
||||
{
|
||||
_locator.Add(typeof(TViewModel), () => new TView());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Platform.Storage;
|
||||
using v2rayN.Desktop.Manager;
|
||||
using v2rayN.Desktop.Views;
|
||||
|
||||
namespace v2rayN.Desktop.Common;
|
||||
@@ -7,16 +8,17 @@ internal class UI
|
||||
{
|
||||
private static readonly string caption = Global.AppName;
|
||||
|
||||
public static async Task<ButtonResult> ShowYesNo(Window owner, string msg)
|
||||
public static async Task<ButtonResult> ShowYesNo(string msg)
|
||||
{
|
||||
var owner = WindowDialog.TryGetOwnerWindow();
|
||||
var box = new MessageBoxDialog(caption, msg);
|
||||
var result = await box.ShowDialog<ButtonResult>(owner);
|
||||
return result == ButtonResult.Yes ? ButtonResult.Yes : ButtonResult.No;
|
||||
}
|
||||
|
||||
public static async Task<string?> OpenFileDialog(Window owner, FilePickerFileType? filter)
|
||||
public static async Task<string?> OpenFileDialog(FilePickerFileType? filter)
|
||||
{
|
||||
var sp = GetStorageProvider(owner);
|
||||
var sp = GetStorageProvider();
|
||||
if (sp is null)
|
||||
{
|
||||
return null;
|
||||
@@ -32,9 +34,9 @@ internal class UI
|
||||
return files.FirstOrDefault()?.TryGetLocalPath();
|
||||
}
|
||||
|
||||
public static async Task<string?> SaveFileDialog(Window owner, string filter)
|
||||
public static async Task<string?> SaveFileDialog(string filter)
|
||||
{
|
||||
var sp = GetStorageProvider(owner);
|
||||
var sp = GetStorageProvider();
|
||||
if (sp is null)
|
||||
{
|
||||
return null;
|
||||
@@ -48,8 +50,9 @@ internal class UI
|
||||
return files?.TryGetLocalPath();
|
||||
}
|
||||
|
||||
private static IStorageProvider? GetStorageProvider(Window owner)
|
||||
private static IStorageProvider? GetStorageProvider()
|
||||
{
|
||||
var owner = WindowDialog.TryGetOwnerWindow();
|
||||
var topLevel = TopLevel.GetTopLevel(owner);
|
||||
return topLevel?.StorageProvider;
|
||||
}
|
||||
|
||||
99
v2rayN/v2rayN.Desktop/DesignData/DesignData.cs
Normal file
99
v2rayN/v2rayN.Desktop/DesignData/DesignData.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using ServiceLib.Models.Entities;
|
||||
using System.Diagnostics;
|
||||
using v2rayN.Desktop.Manager;
|
||||
using v2rayN.Desktop.ViewModels;
|
||||
|
||||
namespace v2rayN.Desktop.DesignData;
|
||||
|
||||
/// <summary>
|
||||
/// Provides design-time data for Avalonia XAML previewer.
|
||||
/// Each inner class lazily initializes <see cref="AppManager"/> with a stub config
|
||||
/// so that ViewModel constructors don't fail during design-time rendering.
|
||||
/// </summary>
|
||||
public static class DesignData
|
||||
{
|
||||
// ── Parameterless-constructor ViewModels ───────────────────────────────
|
||||
|
||||
public static MainWindowViewModel? MainWindow { get; } = SafeCreate(CreateMainWindow);
|
||||
|
||||
public static ProfilesViewModel? Profiles { get; } = SafeCreate(() => new ProfilesViewModel());
|
||||
|
||||
public static StatusBarViewModel? StatusBar { get; } = SafeCreate(CreateStatusBar);
|
||||
|
||||
public static MsgViewModel? Msg { get; } = SafeCreate(() => new MsgViewModel());
|
||||
|
||||
public static SubSettingViewModel? SubSetting { get; } = SafeCreate(() => new SubSettingViewModel());
|
||||
|
||||
public static RoutingSettingViewModel? RoutingSetting { get; } = SafeCreate(() => new RoutingSettingViewModel());
|
||||
|
||||
public static ClashProxiesViewModel? ClashProxies { get; } = SafeCreate(() => new ClashProxiesViewModel());
|
||||
|
||||
public static ClashConnectionsViewModel? ClashConnections { get; } = SafeCreate(() => new ClashConnectionsViewModel());
|
||||
|
||||
public static CheckUpdateViewModel? CheckUpdate { get; } = SafeCreate(() => new CheckUpdateViewModel());
|
||||
|
||||
public static DNSSettingViewModel? DNSSetting { get; } = SafeCreate(() => new DNSSettingViewModel());
|
||||
|
||||
public static FullConfigTemplateViewModel? FullConfigTemplate { get; } = SafeCreate(() => new FullConfigTemplateViewModel());
|
||||
|
||||
public static GlobalHotkeySettingViewModel? GlobalHotkeySetting { get; } = SafeCreate(() => new GlobalHotkeySettingViewModel());
|
||||
|
||||
public static OptionSettingViewModel? OptionSetting { get; } = SafeCreate(() => new OptionSettingViewModel());
|
||||
|
||||
public static ProfilesSelectViewModel? ProfilesSelect { get; } = SafeCreate(() => new ProfilesSelectViewModel());
|
||||
|
||||
public static BackupAndRestoreViewModel? BackupAndRestore { get; } = SafeCreate(() => new BackupAndRestoreViewModel());
|
||||
|
||||
public static ThemeSettingViewModel? ThemeSetting { get; } = SafeCreate(() => new ThemeSettingViewModel());
|
||||
|
||||
// ── ViewModels that require constructor parameters ─────────────────────
|
||||
|
||||
public static AddGroupServerViewModel? AddGroupServer { get; } = SafeCreate(() => new AddGroupServerViewModel(new ProfileItem { Remarks = "Design Group", ConfigType = EConfigType.PolicyGroup }));
|
||||
|
||||
public static AddServer2ViewModel? AddServer2 { get; } = SafeCreate(() => new AddServer2ViewModel(new ProfileItem { Remarks = "Design Custom Server", ConfigType = EConfigType.Custom }));
|
||||
|
||||
public static AddServerViewModel? AddServer { get; } = SafeCreate(() => new AddServerViewModel(new ProfileItem { Remarks = "Design VMess Server", ConfigType = EConfigType.VMess, Address = "example.com", Port = 443 }));
|
||||
|
||||
public static RoutingRuleSettingViewModel? RoutingRuleSetting { get; } = SafeCreate(() => new RoutingRuleSettingViewModel(new RoutingItem { Remarks = "Design Routing Rule" }));
|
||||
|
||||
public static RoutingRuleDetailsViewModel? RoutingRuleDetails { get; } = SafeCreate(() => new RoutingRuleDetailsViewModel(new RulesItem { Domain = ["example.com"], OutboundTag = "direct" }));
|
||||
|
||||
public static SubEditViewModel? SubEdit { get; } = SafeCreate(() => new SubEditViewModel(new SubItem { Remarks = "Design Subscription", Url = "https://example.com/sub" }));
|
||||
|
||||
// ── Helper factories ───────────────────────────────────────────────────
|
||||
|
||||
private static MainWindowViewModel CreateMainWindow()
|
||||
{
|
||||
var vm = new MainWindowViewModel { DesignMode = true };
|
||||
return vm;
|
||||
}
|
||||
|
||||
private static StatusBarViewModel CreateStatusBar()
|
||||
{
|
||||
var vm = StatusBarViewModel.Instance;
|
||||
vm.InboundDisplay = "socks:10808";
|
||||
vm.InboundLanDisplay = "http:10809";
|
||||
vm.RunningServerDisplay = "🚀 Design Server (Active)";
|
||||
vm.RunningInfoDisplay = "v2rayN Design Mode";
|
||||
vm.SpeedProxyDisplay = "↑ 1.2 MB/s";
|
||||
vm.SpeedDirectDisplay = "↓ 5.6 MB/s";
|
||||
vm.RoutingItems.Add(new RoutingItem { Remarks = "Default Routing" });
|
||||
vm.RoutingItems.Add(new RoutingItem { Remarks = "Global" });
|
||||
return vm;
|
||||
}
|
||||
|
||||
private static T? SafeCreate<T>(Func<T> factory) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
AppManager.Instance.InitApp();
|
||||
AppManager.Instance.WindowDialog = new WindowDialog();
|
||||
return factory();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[DesignData] Failed to create {typeof(T).Name}: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ global using System.Collections.Generic;
|
||||
global using System.Globalization;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Reactive;
|
||||
global using System.Reactive.Disposables.Fluent;
|
||||
global using System.Reactive.Linq;
|
||||
global using System.Runtime.Versioning;
|
||||
|
||||
68
v2rayN/v2rayN.Desktop/Manager/WindowDialog.cs
Normal file
68
v2rayN/v2rayN.Desktop/Manager/WindowDialog.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop.Manager;
|
||||
|
||||
public class WindowDialog : IWindowDialog
|
||||
{
|
||||
public async Task<bool> ShowDialogAsync<TViewModel>(TViewModel vm)
|
||||
where TViewModel : class
|
||||
{
|
||||
var owner = TryGetOwnerWindow();
|
||||
|
||||
var view = SimpleViewLocator.Instance.Build(vm);
|
||||
|
||||
if (view is not WindowBase<TViewModel> window)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
window.ViewModel = vm;
|
||||
|
||||
if (vm is ServiceLib.Base.ICloseable closeable)
|
||||
{
|
||||
closeable.RequestClose += (_, _) => Dispatch(() => window.Close(true));
|
||||
}
|
||||
|
||||
var result = await window.ShowDialog<bool>(owner);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Window TryGetOwnerWindow()
|
||||
{
|
||||
var desktop = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime
|
||||
?? throw new InvalidOperationException("Application lifetime is not of type IClassicDesktopStyleApplicationLifetime.");
|
||||
var openWindows = desktop.Windows
|
||||
.Where(w => w.IsVisible)
|
||||
.ToArray();
|
||||
if (openWindows.Length == 0)
|
||||
{
|
||||
return desktop.MainWindow
|
||||
?? throw new InvalidOperationException("No open windows and no main window found.");
|
||||
}
|
||||
|
||||
if (openWindows.Length == 1)
|
||||
{
|
||||
return openWindows[0];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Window.SortWindowsByZOrder(openWindows);
|
||||
|
||||
var activeTopmost = openWindows.Reverse().FirstOrDefault(w => w.IsActive);
|
||||
return activeTopmost ?? openWindows[^1];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return desktop.Windows.FirstOrDefault(w => w.IsActive)
|
||||
?? desktop.MainWindow
|
||||
?? openWindows[0];
|
||||
}
|
||||
}
|
||||
|
||||
private static void Dispatch(Action action)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(action);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using v2rayN.Desktop.Common;
|
||||
using v2rayN.Desktop.Manager;
|
||||
|
||||
namespace v2rayN.Desktop;
|
||||
|
||||
@@ -48,6 +49,8 @@ internal class Program
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AppManager.Instance.WindowDialog = new WindowDialog();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -58,6 +61,9 @@ internal class Program
|
||||
.UsePlatformDetect()
|
||||
//.WithInterFont()
|
||||
.WithFontByDefault()
|
||||
#if DEBUG
|
||||
.WithDeveloperTools()
|
||||
#endif
|
||||
.LogToTrace()
|
||||
.UseReactiveUI(_ => { });
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
@@ -10,6 +11,7 @@
|
||||
Width="900"
|
||||
Height="700"
|
||||
x:DataType="vms:AddGroupServerViewModel"
|
||||
Design.DataContext="{x:Static dd:DesignData.AddGroupServer}"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace v2rayN.Desktop.Views;
|
||||
public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
{
|
||||
public AddGroupServerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AddGroupServerWindow(ProfileItem profileItem)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@@ -18,8 +13,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
lstChild.SelectionChanged += LstChild_SelectionChanged;
|
||||
tabControl.SelectionChanged += TabControl_SelectionChanged;
|
||||
|
||||
ViewModel = new AddGroupServerViewModel(profileItem, UpdateViewHandler);
|
||||
|
||||
cmbCoreType.ItemsSource = Global.CoreTypes;
|
||||
cmbPolicyGroupType.ItemsSource = new List<string>
|
||||
{
|
||||
@@ -31,6 +24,42 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
};
|
||||
cmbFilter.ItemsSource = Global.PolicyGroupDefaultFilterList;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
|
||||
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.AddCmd, v => v.menuAddChildServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.RemoveCmd, v => v.menuRemoveChildServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
// Context menu actions that require custom logic (Add, SelectAll)
|
||||
menuSelectAllChild.Click += (s, e) => lstChild.SelectAll();
|
||||
|
||||
// Keyboard shortcuts when focus is within grid
|
||||
AddHandler(KeyDownEvent, AddGroupServerWindow_KeyDown, RoutingStrategies.Tunnel);
|
||||
lstChild.LoadingRow += LstChild_LoadingRow;
|
||||
}
|
||||
|
||||
private void InitializeData(ProfileItem profileItem)
|
||||
{
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
case EConfigType.PolicyGroup:
|
||||
@@ -46,34 +75,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.PolicyGroupType, v => v.cmbPolicyGroupType.SelectedValue).DisposeWith(disposables);
|
||||
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbSubChildItems.ItemsSource).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSubItem, v => v.cmbSubChildItems.SelectedItem).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Filter, v => v.cmbFilter.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedChild, v => v.lstChild.SelectedItem).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.RemoveCmd, v => v.menuRemoveChildServer).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
// Context menu actions that require custom logic (Add, SelectAll)
|
||||
menuAddChildServer.Click += MenuAddChild_Click;
|
||||
menuSelectAllChild.Click += (s, e) => lstChild.SelectAll();
|
||||
|
||||
// Keyboard shortcuts when focus is within grid
|
||||
AddHandler(KeyDownEvent, AddGroupServerWindow_KeyDown, RoutingStrategies.Tunnel);
|
||||
lstChild.LoadingRow += LstChild_LoadingRow;
|
||||
}
|
||||
|
||||
private void LstChild_LoadingRow(object? sender, DataGridRowEventArgs e)
|
||||
@@ -81,17 +82,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
e.Row.Header = $" {e.Row.Index + 1}";
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
Close(true);
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
txtRemarks.Focus();
|
||||
@@ -145,19 +135,6 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
|
||||
}
|
||||
}
|
||||
|
||||
private async void MenuAddChild_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectWindow = new ProfilesSelectWindow();
|
||||
selectWindow.SetConfigTypeFilter([EConfigType.Custom], exclude: true);
|
||||
selectWindow.AllowMultiSelect(true);
|
||||
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||
if (result == true)
|
||||
{
|
||||
var profiles = await selectWindow.ProfileItems;
|
||||
ViewModel?.ChildItemsObs.AddRange(profiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void LstChild_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel != null)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
@@ -10,6 +11,7 @@
|
||||
Width="700"
|
||||
Height="500"
|
||||
x:DataType="vms:AddServer2ViewModel"
|
||||
Design.DataContext="{x:Static dd:DesignData.AddServer2}"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -6,17 +6,11 @@ namespace v2rayN.Desktop.Views;
|
||||
public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
|
||||
{
|
||||
public AddServer2Window()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AddServer2Window(ProfileItem profileItem)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += Window_Loaded;
|
||||
btnCancel.Click += (s, e) => Close();
|
||||
ViewModel = new AddServer2ViewModel(profileItem, UpdateViewHandler);
|
||||
|
||||
cmbCoreType.ItemsSource = Utils.GetEnumNames<ECoreType>().Where(t => t != nameof(ECoreType.v2rayN)).ToList().AppendEmpty();
|
||||
|
||||
@@ -31,30 +25,15 @@ public partial class AddServer2Window : WindowBase<AddServer2ViewModel>
|
||||
this.BindCommand(ViewModel, vm => vm.BrowseServerCmd, v => v.btnBrowse).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.btnEdit).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SaveServerCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
|
||||
ViewModel.BrowseConfigFileInteraction.RegisterHandler(async interaction =>
|
||||
{
|
||||
var fileName = await UI.OpenFileDialog(null);
|
||||
interaction.SetOutput(fileName);
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
Close(true);
|
||||
break;
|
||||
|
||||
case EViewAction.BrowseServer:
|
||||
var fileName = await UI.OpenFileDialog(this, null);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ViewModel?.BrowseServer(fileName);
|
||||
break;
|
||||
}
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
txtRemarks.Focus();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:views="clr-namespace:v2rayN.Desktop.Views"
|
||||
@@ -11,6 +12,7 @@
|
||||
Width="900"
|
||||
Height="600"
|
||||
x:DataType="vms:AddServerViewModel"
|
||||
Design.DataContext="{x:Static dd:DesignData.AddServer}"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
@@ -256,6 +258,27 @@
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="tbHttpHeaders"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="False"
|
||||
Text="{x:Static resx:ResUI.TbHttpOutboundHeaders}"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.TipHttpOutboundHeaders}" />
|
||||
<views:JsonEditor
|
||||
x:Name="txtHttpHeadersJson"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
MinHeight="100"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="False"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.TipHttpOutboundHeaders}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridVLESS"
|
||||
@@ -422,12 +445,12 @@
|
||||
x:Name="txtMinGeckoPacketSize"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Min" />
|
||||
PlaceholderText="Min" />
|
||||
<TextBox
|
||||
x:Name="txtMaxGeckoPacketSize"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Max" />
|
||||
PlaceholderText="Max" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Margin="{StaticResource Margin4}"
|
||||
@@ -460,7 +483,7 @@
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="1000-2000,3000,4000" />
|
||||
PlaceholderText="1000-2000,3000,4000" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
@@ -498,12 +521,12 @@
|
||||
x:Name="txtUpMbps7"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Up" />
|
||||
PlaceholderText="Up" />
|
||||
<TextBox
|
||||
x:Name="txtDownMbps7"
|
||||
Width="90"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Down" />
|
||||
PlaceholderText="Down" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid
|
||||
@@ -612,7 +635,7 @@
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="0, 0, 0" />
|
||||
PlaceholderText="0, 0, 0" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
@@ -626,7 +649,7 @@
|
||||
Grid.Column="1"
|
||||
Width="400"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Watermark="Ipv4,Ipv6" />
|
||||
PlaceholderText="Ipv4,Ipv6" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
@@ -641,7 +664,7 @@
|
||||
Width="200"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left"
|
||||
Watermark="1280" />
|
||||
PlaceholderText="1280" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridAnytls"
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Reactive.Disposables;
|
||||
using v2rayN.Desktop.Base;
|
||||
using v2rayN.Desktop.Common;
|
||||
|
||||
namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
{
|
||||
public AddServerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AddServerWindow(ProfileItem profileItem)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@@ -21,8 +18,6 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
btnGUID.Click += btnGUID_Click;
|
||||
btnGUID5.Click += btnGUID_Click;
|
||||
|
||||
ViewModel = new AddServerViewModel(profileItem, UpdateViewHandler);
|
||||
|
||||
cmbCoreType.ItemsSource = Global.CoreTypes.AppendEmpty();
|
||||
cmbNetwork.ItemsSource = Global.Networks;
|
||||
|
||||
@@ -39,8 +34,173 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
cmbFingerprint2.ItemsSource = Global.Fingerprints;
|
||||
cmbAlpn.ItemsSource = Global.Alpns;
|
||||
|
||||
var lstStreamSecurity = new List<string> { string.Empty, Global.StreamSecurity };
|
||||
gridTlsMore.IsVisible = false;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource)
|
||||
.WhereNotNull()
|
||||
.Subscribe(InitializeData)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
var configTypeBindings = new SerialDisposable().DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Address, v => v.txtAddress.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
||||
|
||||
this.WhenAnyValue(v => v.ViewModel.SelectedSource.ConfigType)
|
||||
.Subscribe(configType =>
|
||||
{
|
||||
var currentTypeDisposables = new CompositeDisposable();
|
||||
configTypeBindings.Disposable = currentTypeDisposables;
|
||||
|
||||
switch (configType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.SOCKS:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text)
|
||||
.DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.HTTP:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.HttpHeadersJson, v => v.txtHttpHeadersJson.Text)
|
||||
.DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Trojan:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Hysteria2:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Hy2RealmUrl, v => v.txtHy2RealmUrl.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMinPacketSize, v => v.txtMinGeckoPacketSize.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMaxPacketSize, v => v.txtMaxGeckoPacketSize.Text).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.TUIC:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl8.SelectedValue).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.WireGuard:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.WgPresharedKey, v => v.txtPreSharedKey9.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Anytls:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId11.Text).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Naive:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId12.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity12.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.togNaiveQuic12.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.cmbCongestionControl12.IsEnabled).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl12.SelectedValue).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(currentTypeDisposables);
|
||||
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(currentTypeDisposables);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RawHeaderType, v => v.cmbHeaderTypeRaw.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostRaw.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathRaw.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.KcpHeaderType, v => v.cmbHeaderTypeKcp.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.KcpSeed, v => v.txtKcpSeed.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.KcpMtu, v => v.txtKcpMtu.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostWs.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathWs.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostHttpupgrade.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathHttpupgrade.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.XhttpMode, v => v.cmbHeaderTypeXhttp.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostXhttp.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathXhttp.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.XhttpExtra, v => v.txtExtraXhttp.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.GrpcMode, v => v.cmbHeaderTypeGrpc.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GrpcAuthority, v => v.txtRequestHostGrpc.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GrpcServiceName, v => v.txtPathGrpc.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.StreamSecurity, v => v.cmbStreamSecurity.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AllowInsecure, v => v.togAllowInsecure.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.VerifyPeerCertByName, v => v.txtVerifyPeerCertByName.Text).DisposeWith(disposables);
|
||||
|
||||
//reality
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void InitializeData(ProfileItem profileItem)
|
||||
{
|
||||
Title = $"{profileItem.ConfigType}";
|
||||
var lstStreamSecurity = new List<string> { string.Empty, Global.StreamSecurity };
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
@@ -54,8 +214,13 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
break;
|
||||
|
||||
case EConfigType.SOCKS:
|
||||
gridSocks.IsVisible = true;
|
||||
break;
|
||||
|
||||
case EConfigType.HTTP:
|
||||
gridSocks.IsVisible = true;
|
||||
tbHttpHeaders.IsVisible = true;
|
||||
txtHttpHeadersJson.IsVisible = true;
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
@@ -121,154 +286,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||
break;
|
||||
}
|
||||
cmbStreamSecurity.ItemsSource = lstStreamSecurity;
|
||||
|
||||
gridTlsMore.IsVisible = false;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.CoreType, v => v.cmbCoreType.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Address, v => v.txtAddress.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
||||
|
||||
switch (profileItem.ConfigType)
|
||||
{
|
||||
case EConfigType.VMess:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AlterId, v => v.txtAlterId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.VmessSecurity, v => v.cmbSecurity.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Shadowsocks:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.SOCKS:
|
||||
case EConfigType.HTTP:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId4.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtSecurity4.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.VLESS:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow5.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.VlessEncryption, v => v.txtSecurity5.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled5.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Trojan:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId6.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Flow, v => v.cmbFlow6.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.MuxEnabled, v => v.togmuxEnabled6.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Hysteria2:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SalamanderPass, v => v.txtPath7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Ports, v => v.txtPorts7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.HopInterval, v => v.txtHopInt7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.UpMbps, v => v.txtUpMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.DownMbps, v => v.txtDownMbps7.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Hy2RealmUrl, v => v.txtHy2RealmUrl.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMinPacketSize, v => v.txtMinGeckoPacketSize.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GeckoMaxPacketSize, v => v.txtMaxGeckoPacketSize.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.TUIC:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl8.SelectedValue).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.WireGuard:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgPublicKey, v => v.txtPublicKey9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgPresharedKey, v => v.txtPreSharedKey9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgReserved, v => v.txtPath9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgInterfaceAddress, v => v.txtRequestHost9.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.WgMtu, v => v.txtShortId9.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Anytls:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId11.Text).DisposeWith(disposables);
|
||||
break;
|
||||
|
||||
case EConfigType.Naive:
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId12.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity12.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.togNaiveQuic12.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.NaiveQuic, v => v.cmbCongestionControl12.IsEnabled).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbCongestionControl12.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.InsecureConcurrency, v => v.txtInsecureConcurrency12.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled12.IsChecked).DisposeWith(disposables);
|
||||
break;
|
||||
}
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Network, v => v.cmbNetwork.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.RawHeaderType, v => v.cmbHeaderTypeRaw.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostRaw.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathRaw.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.KcpHeaderType, v => v.cmbHeaderTypeKcp.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.KcpSeed, v => v.txtKcpSeed.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.KcpMtu, v => v.txtKcpMtu.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostWs.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathWs.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostHttpupgrade.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathHttpupgrade.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.XhttpMode, v => v.cmbHeaderTypeXhttp.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Host, v => v.txtRequestHostXhttp.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Path, v => v.txtPathXhttp.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.XhttpExtra, v => v.txtExtraXhttp.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.GrpcMode, v => v.cmbHeaderTypeGrpc.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GrpcAuthority, v => v.txtRequestHostGrpc.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.GrpcServiceName, v => v.txtPathGrpc.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.StreamSecurity, v => v.cmbStreamSecurity.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AllowInsecure, v => v.togAllowInsecure.IsChecked).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CertSha, v => v.txtCertSha256Pinning.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.Cert, v => v.txtCert.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.EchConfigList, v => v.txtEchConfigList.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.VerifyPeerCertByName, v => v.txtVerifyPeerCertByName.Text).DisposeWith(disposables);
|
||||
|
||||
//reality
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.PublicKey, v => v.txtPublicKey.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.ShortId, v => v.txtShortId.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||
});
|
||||
|
||||
Title = $"{profileItem.ConfigType}";
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case EViewAction.CloseWindow:
|
||||
Close(true);
|
||||
break;
|
||||
}
|
||||
return await Task.FromResult(true);
|
||||
cmbStreamSecurity.SelectedItem = profileItem.StreamSecurity;
|
||||
}
|
||||
|
||||
private void Window_Loaded(object? sender, RoutedEventArgs e)
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Design.DataContext="{x:Static dd:DesignData.BackupAndRestore}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Button">
|
||||
@@ -18,8 +20,8 @@
|
||||
<StackPanel Margin="{StaticResource Margin4}" DockPanel.Dock="Bottom">
|
||||
<TextBlock
|
||||
Name="txtMsg"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel>
|
||||
@@ -41,15 +43,15 @@
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.menuLocalBackup}" />
|
||||
<Button
|
||||
Name="menuLocalBackup"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Static resx:ResUI.menuLocalBackup}" />
|
||||
|
||||
<Separator
|
||||
@@ -61,15 +63,15 @@
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.menuLocalRestore}" />
|
||||
<Button
|
||||
Name="menuLocalRestore"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Static resx:ResUI.menuLocalRestore}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -87,11 +89,9 @@
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Margin="{StaticResource Margin4}" Text="{x:Static resx:ResUI.menuRemoteBackupAndRestore}" />
|
||||
|
||||
<Button
|
||||
Classes="IconButton"
|
||||
Margin="{StaticResource MarginLr8}">
|
||||
<Button Margin="{StaticResource MarginLr8}" Classes="IconButton">
|
||||
<Button.Content>
|
||||
<PathIcon Data="{StaticResource SemiIconMore}" >
|
||||
<PathIcon Data="{StaticResource SemiIconMore}">
|
||||
<PathIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" />
|
||||
</PathIcon.RenderTransform>
|
||||
@@ -104,67 +104,67 @@
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvWebDavUrl}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtWebDavUrl"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvWebDavUserName}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtWebDavUserName"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvWebDavPassword}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtWebDavPassword"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.LvWebDavDirName}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="txtWebDavDirName"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}" />
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<Button
|
||||
x:Name="menuWebDavCheck"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin4}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
Content="{x:Static resx:ResUI.LvWebDavCheck}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
@@ -177,15 +177,15 @@
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.menuRemoteBackup}" />
|
||||
<Button
|
||||
Name="menuRemoteBackup"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Static resx:ResUI.menuRemoteBackup}" />
|
||||
|
||||
<Separator
|
||||
@@ -196,15 +196,15 @@
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.menuRemoteRestore}" />
|
||||
<Button
|
||||
Name="menuRemoteRestore"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
Content="{x:Static resx:ResUI.menuRemoteRestore}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -4,23 +4,12 @@ namespace v2rayN.Desktop.Views;
|
||||
|
||||
public partial class BackupAndRestoreView : ReactiveUserControl<BackupAndRestoreViewModel>
|
||||
{
|
||||
private Window? _window;
|
||||
|
||||
public BackupAndRestoreView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public BackupAndRestoreView(Window window)
|
||||
{
|
||||
_window = window;
|
||||
|
||||
InitializeComponent();
|
||||
menuLocalBackup.Click += MenuLocalBackup_Click;
|
||||
menuLocalRestore.Click += MenuLocalRestore_Click;
|
||||
|
||||
ViewModel = new BackupAndRestoreViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.OperationMsg, v => v.txtMsg.Text).DisposeWith(disposables);
|
||||
@@ -39,7 +28,7 @@ public partial class BackupAndRestoreView : ReactiveUserControl<BackupAndRestore
|
||||
|
||||
private async void MenuLocalBackup_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var fileName = await UI.SaveFileDialog(_window, "Zip|*.zip");
|
||||
var fileName = await UI.SaveFileDialog("Zip|*.zip");
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
@@ -50,7 +39,7 @@ public partial class BackupAndRestoreView : ReactiveUserControl<BackupAndRestore
|
||||
|
||||
private async void MenuLocalRestore_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var fileName = await UI.OpenFileDialog(_window, null);
|
||||
var fileName = await UI.OpenFileDialog(null);
|
||||
if (fileName.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
@@ -58,9 +47,4 @@ public partial class BackupAndRestoreView : ReactiveUserControl<BackupAndRestore
|
||||
|
||||
ViewModel?.LocalRestore(fileName);
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
d:DesignHeight="600"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="vms:CheckUpdateViewModel"
|
||||
Design.DataContext="{x:Static dd:DesignData.CheckUpdate}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<DockPanel Margin="{StaticResource Margin8}">
|
||||
|
||||
@@ -6,8 +6,6 @@ public partial class CheckUpdateView : ReactiveUserControl<CheckUpdateViewModel>
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new CheckUpdateViewModel(UpdateViewHandler);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.CheckUpdateModels, v => v.lstCheckUpdates.ItemsSource).DisposeWith(disposables);
|
||||
@@ -17,9 +15,4 @@ public partial class CheckUpdateView : ReactiveUserControl<CheckUpdateViewModel>
|
||||
this.BindCommand(ViewModel, vm => vm.CheckUpdateCmd, v => v.btnCheckUpdate).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,34 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="vms:ClashConnectionsViewModel"
|
||||
Design.DataContext="{x:Static dd:DesignData.ClashConnections}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<DockPanel Margin="2">
|
||||
<WrapPanel
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin4}"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Top"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBox
|
||||
x:Name="txtHostFilter"
|
||||
Width="200"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Watermark="{x:Static resx:ResUI.ConnectionsHostFilterTitle}" />
|
||||
VerticalContentAlignment="Center"
|
||||
PlaceholderText="{x:Static resx:ResUI.ConnectionsHostFilterTitle}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnConnectionCloseAll"
|
||||
Classes="IconButton Success"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Classes="IconButton Success"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.menuConnectionCloseAll}">
|
||||
<Button.Content>
|
||||
<PathIcon Data="{StaticResource SemiIconClose}" />
|
||||
@@ -37,8 +39,8 @@
|
||||
|
||||
<Button
|
||||
x:Name="btnAutofitColumnWidth"
|
||||
Classes="IconButton Success"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
Classes="IconButton Success"
|
||||
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
|
||||
<Button.Content>
|
||||
<PathIcon Data="{StaticResource SemiIconExpand}" />
|
||||
@@ -46,13 +48,13 @@
|
||||
</Button>
|
||||
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static resx:ResUI.TbAutoRefresh}" />
|
||||
<ToggleSwitch
|
||||
x:Name="togAutoRefresh"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource MarginLr8}" />
|
||||
Margin="{StaticResource MarginLr8}"
|
||||
HorizontalAlignment="Left" />
|
||||
</WrapPanel>
|
||||
|
||||
<DataGrid
|
||||
|
||||
@@ -11,7 +11,6 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
|
||||
|
||||
_config = AppManager.Instance.Config;
|
||||
|
||||
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
|
||||
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
@@ -36,11 +35,6 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
|
||||
RestoreUI();
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
AutofitColumnWidth();
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:conv="using:v2rayN.Desktop.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dd="clr-namespace:v2rayN.Desktop.DesignData"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:DataType="vms:ClashProxiesViewModel"
|
||||
Design.DataContext="{x:Static dd:DesignData.ClashProxies}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<conv:DelayColorConverter x:Key="DelayColorConverter" />
|
||||
|
||||
@@ -5,7 +5,6 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
|
||||
public ClashProxiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
|
||||
lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped;
|
||||
KeyDown += ClashProxiesView_KeyDown;
|
||||
|
||||
@@ -29,11 +28,6 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void ClashProxiesView_KeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user