mirror of
https://github.com/2dust/v2rayN.git
synced 2026-01-15 10:29:33 +00:00
Compare commits
29 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe183798b6 | ||
|
|
947c84cf10 | ||
|
|
9c74b51d74 | ||
|
|
abd962ab31 | ||
|
|
f3b894015e | ||
|
|
4562d4cf00 | ||
|
|
bc36cf8a47 | ||
|
|
cbdfe2e15a | ||
|
|
68583e20bc | ||
|
|
6d6459b009 | ||
|
|
807562b69e | ||
|
|
654d7d83d0 | ||
|
|
027252e687 | ||
|
|
5478c90180 | ||
|
|
28f30d7e97 | ||
|
|
ae7d54c2e5 | ||
|
|
56d0d65b06 | ||
|
|
5e8e189c27 | ||
|
|
3fee86d44a | ||
|
|
dd77eb79c6 | ||
|
|
d26a2559a6 | ||
|
|
e5ba1759aa | ||
|
|
bfdee37cc1 | ||
|
|
cf89cfcd95 | ||
|
|
39a988c704 | ||
|
|
2b28254fbc | ||
|
|
6e27dca6cd | ||
|
|
7cee98887b | ||
|
|
3885ff8b31 |
63 changed files with 948 additions and 593 deletions
6
.github/workflows/build-linux.yml
vendored
6
.github/workflows/build-linux.yml
vendored
|
|
@ -50,7 +50,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64"
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v5.0.0
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-linux
|
name: v2rayN-linux
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -116,7 +116,7 @@ jobs:
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Restore build artifacts
|
- name: Restore build artifacts
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: v2rayN-linux
|
name: v2rayN-linux
|
||||||
path: ${{ github.workspace }}/v2rayN/Release
|
path: ${{ github.workspace }}/v2rayN/Release
|
||||||
|
|
@ -137,7 +137,7 @@ jobs:
|
||||||
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
ls -R "$GITHUB_WORKSPACE/dist/rpm" || true
|
||||||
|
|
||||||
- name: Upload RPM artifacts
|
- name: Upload RPM artifacts
|
||||||
uses: actions/upload-artifact@v5.0.0
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-rpm
|
name: v2rayN-rpm
|
||||||
path: dist/rpm/**/*.rpm
|
path: dist/rpm/**/*.rpm
|
||||||
|
|
|
||||||
2
.github/workflows/build-osx.yml
vendored
2
.github/workflows/build-osx.yml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v5.0.0
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-macos
|
name: v2rayN-macos
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
2
.github/workflows/build-windows-desktop.yml
vendored
2
.github/workflows/build-windows-desktop.yml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v5.0.0
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-windows-desktop
|
name: v2rayN-windows-desktop
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
10
.github/workflows/build-windows.yml
vendored
10
.github/workflows/build-windows.yml
vendored
|
|
@ -37,16 +37,16 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd v2rayN
|
cd v2rayN
|
||||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64
|
||||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
||||||
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
dotnet publish ./v2rayN/v2rayN.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -o $OutputPath64Sc
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPath64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=false -p:EnableWindowsTargeting=true -o $OutputPathArm64
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64
|
||||||
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
|
dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-x64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPath64Sc
|
||||||
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v5.0.0
|
uses: actions/upload-artifact@v6.0.0
|
||||||
with:
|
with:
|
||||||
name: v2rayN-windows
|
name: v2rayN-windows
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
|
# ====== Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS ======
|
||||||
if [[ -r /etc/os-release ]]; then
|
if [[ -r /etc/os-release ]]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
case "$ID" in
|
case "$ID" in
|
||||||
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
|
rhel|rocky|almalinux|fedora|centos)
|
||||||
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|
@ -30,7 +30,7 @@ echo "[INFO] Detected kernel version: $KERNEL_FULL"
|
||||||
|
|
||||||
if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then
|
if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then
|
||||||
echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
|
echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}."
|
||||||
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+, Debian 13+)."
|
echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+)."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -80,7 +80,6 @@ host_arch="$(uname -m)"
|
||||||
|
|
||||||
install_ok=0
|
install_ok=0
|
||||||
case "$ID" in
|
case "$ID" in
|
||||||
# ------------------------------ RHEL family (UNCHANGED) ------------------------------
|
|
||||||
rhel|rocky|almalinux|centos)
|
rhel|rocky|almalinux|centos)
|
||||||
if command -v dnf >/dev/null 2>&1; then
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \
|
||||||
|
|
@ -92,58 +91,7 @@ case "$ID" in
|
||||||
install_ok=1
|
install_ok=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
# ------------------------------ Ubuntu ----------------------------------------------
|
*)
|
||||||
ubuntu)
|
|
||||||
sudo apt-get update
|
|
||||||
# Ensure 'universe' (Ubuntu) to get 'rpm'
|
|
||||||
if ! apt-cache policy | grep -q '^500 .*ubuntu.com/ubuntu.* universe'; then
|
|
||||||
sudo apt-get -y install software-properties-common || true
|
|
||||||
sudo add-apt-repository -y universe || true
|
|
||||||
sudo apt-get update
|
|
||||||
fi
|
|
||||||
# Base tools + rpm (provides rpmbuild)
|
|
||||||
sudo apt-get -y install curl unzip tar rsync rpm || true
|
|
||||||
# Cross-arch binutils so strip matches target arch + objdump for brp scripts
|
|
||||||
sudo apt-get -y install binutils binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu || true
|
|
||||||
# rpmbuild presence check
|
|
||||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
|
||||||
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
|
||||||
echo " Please ensure the 'rpm' package is available from your repos (universe on Ubuntu)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# .NET SDK 8 (best effort via apt)
|
|
||||||
if ! command -v dotnet >/dev/null 2>&1; then
|
|
||||||
sudo apt-get -y install dotnet-sdk-8.0 || true
|
|
||||||
sudo apt-get -y install dotnet-sdk-8 || true
|
|
||||||
sudo apt-get -y install dotnet-sdk || true
|
|
||||||
fi
|
|
||||||
install_ok=1
|
|
||||||
;;
|
|
||||||
# ------------------------------ Debian (KEEP, with local dotnet install) ------------
|
|
||||||
debian)
|
|
||||||
sudo apt-get update
|
|
||||||
# Base tools + rpm (provides rpmbuild on Debian) + objdump/strip
|
|
||||||
sudo apt-get -y install curl unzip tar rsync rpm binutils || true
|
|
||||||
# rpmbuild presence check
|
|
||||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
|
||||||
echo "[ERROR] 'rpmbuild' not found after installing 'rpm'."
|
|
||||||
echo " Please ensure 'rpm' is available from Debian repos."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Try apt for dotnet; fallback to official installer into $HOME/.dotnet
|
|
||||||
if ! command -v dotnet >/dev/null 2>&1; then
|
|
||||||
echo "[INFO] 'dotnet' not found. Installing .NET 8 SDK locally to \$HOME/.dotnet ..."
|
|
||||||
tmp="$(mktemp -d)"; trap '[[ -n "${tmp:-}" ]] && rm -rf "$tmp"' RETURN
|
|
||||||
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$tmp/dotnet-install.sh"
|
|
||||||
bash "$tmp/dotnet-install.sh" --channel 8.0 --install-dir "$HOME/.dotnet"
|
|
||||||
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
|
|
||||||
export DOTNET_ROOT="$HOME/.dotnet"
|
|
||||||
if ! command -v dotnet >/dev/null 2>&1; then
|
|
||||||
echo "[ERROR] dotnet installation failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
install_ok=1
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
@ -154,7 +102,7 @@ fi
|
||||||
|
|
||||||
command -v curl >/dev/null
|
command -v curl >/dev/null
|
||||||
|
|
||||||
# Root directory = the script's location
|
# Root directory
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
|
@ -164,14 +112,14 @@ if [[ -f .gitmodules ]]; then
|
||||||
git submodule update --init --recursive || true
|
git submodule update --init --recursive || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ===== Locate project ================================================================
|
# Locate project
|
||||||
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
if [[ ! -f "$PROJECT" ]]; then
|
if [[ ! -f "$PROJECT" ]]; then
|
||||||
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
||||||
fi
|
fi
|
||||||
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
||||||
|
|
||||||
# ===== Resolve GUI version & auto checkout ============================================
|
# Resolve GUI version & auto checkout
|
||||||
VERSION=""
|
VERSION=""
|
||||||
|
|
||||||
choose_channel() {
|
choose_channel() {
|
||||||
|
|
@ -391,22 +339,6 @@ download_singbox() {
|
||||||
install -Dm755 "$bin" "$outdir/sing-box"
|
install -Dm755 "$bin" "$outdir/sing-box"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- NEW: download_mihomo (REQUIRED in --netcore mode) ----
|
|
||||||
download_mihomo() {
|
|
||||||
# Download mihomo into outroot/bin/mihomo/mihomo
|
|
||||||
local outroot="$1"
|
|
||||||
local url=""
|
|
||||||
if [[ "$RID_DIR" == "linux-arm64" ]]; then
|
|
||||||
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64/bin/mihomo/mihomo"
|
|
||||||
else
|
|
||||||
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64/bin/mihomo/mihomo"
|
|
||||||
fi
|
|
||||||
echo "[+] Download mihomo: $url"
|
|
||||||
mkdir -p "$outroot/bin/mihomo"
|
|
||||||
curl -fL "$url" -o "$outroot/bin/mihomo/mihomo"
|
|
||||||
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Move geo files to a unified path: outroot/bin
|
# Move geo files to a unified path: outroot/bin
|
||||||
unify_geo_layout() {
|
unify_geo_layout() {
|
||||||
local outroot="$1"
|
local outroot="$1"
|
||||||
|
|
@ -491,8 +423,7 @@ download_v2rayn_bundle() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
||||||
# keep mihomo
|
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
# find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
|
||||||
|
|
||||||
local nested_dir
|
local nested_dir
|
||||||
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
||||||
|
|
@ -603,7 +534,7 @@ build_for_arch() {
|
||||||
fi
|
fi
|
||||||
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
|
# ---- REQUIRED: always fetch mihomo in netcore mode, per-arch ----
|
||||||
download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
|
# download_mihomo "$WORKDIR/$PKGROOT" || echo "[!] mihomo download failed (skipped)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Tarball
|
# Tarball
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.16.6</Version>
|
<Version>7.17.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,23 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.9" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.10" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.9" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.10" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.9" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.10" />
|
||||||
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.10.0" />
|
<PackageVersion Include="CliWrap" Version="3.10.0" />
|
||||||
<PackageVersion Include="Downloader" Version="4.0.3" />
|
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
|
||||||
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
|
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
|
||||||
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.0" />
|
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
|
||||||
<PackageVersion Include="QRCoder" Version="1.7.0" />
|
<PackageVersion Include="QRCoder" Version="1.7.0" />
|
||||||
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
|
<PackageVersion Include="ReactiveUI" Version="22.3.1" />
|
||||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||||
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
|
<PackageVersion Include="ReactiveUI.WPF" Version="22.3.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.1" />
|
<PackageVersion Include="Semi.Avalonia" Version="11.3.7.2" />
|
||||||
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.1" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7.2" />
|
||||||
<PackageVersion Include="NLog" Version="6.0.6" />
|
<PackageVersion Include="NLog" Version="6.0.7" />
|
||||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
<PackageVersion Include="TaskScheduler" Version="2.12.2" />
|
||||||
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
<PackageVersion Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ public static class Extension
|
||||||
{
|
{
|
||||||
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
|
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value)
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value);
|
return string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value);
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? value)
|
|
||||||
{
|
|
||||||
return string.IsNullOrWhiteSpace(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
|
public static bool IsNotEmpty([NotNullWhen(false)] this string? value)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(value);
|
return !string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? NullIfEmpty(this string? value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
public static bool BeginWithAny(this string s, IEnumerable<char> chars)
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ public class Global
|
||||||
public const string SingboxLocalDNSTag = "local_local";
|
public const string SingboxLocalDNSTag = "local_local";
|
||||||
public const string SingboxHostsDNSTag = "hosts_dns";
|
public const string SingboxHostsDNSTag = "hosts_dns";
|
||||||
public const string SingboxFakeDNSTag = "fake_dns";
|
public const string SingboxFakeDNSTag = "fake_dns";
|
||||||
|
public const string SingboxEchDNSTag = "ech_dns";
|
||||||
|
|
||||||
public static readonly List<string> IEProxyProtocols =
|
public static readonly List<string> IEProxyProtocols =
|
||||||
[
|
[
|
||||||
|
|
@ -107,7 +108,9 @@ public class Global
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly List<string> SubConvertConfig =
|
public static readonly List<string> SubConvertConfig =
|
||||||
[@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"];
|
[
|
||||||
|
@"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online.ini"
|
||||||
|
];
|
||||||
|
|
||||||
public static readonly List<string> SubConvertTargets =
|
public static readonly List<string> SubConvertTargets =
|
||||||
[
|
[
|
||||||
|
|
@ -626,5 +629,13 @@ public class Global
|
||||||
""
|
""
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> EchForceQuerys =
|
||||||
|
[
|
||||||
|
"none",
|
||||||
|
"half",
|
||||||
|
"full",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
#endregion const
|
#endregion const
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -253,6 +253,8 @@ public static class ConfigHandler
|
||||||
item.Extra = profileItem.Extra;
|
item.Extra = profileItem.Extra;
|
||||||
item.MuxEnabled = profileItem.MuxEnabled;
|
item.MuxEnabled = profileItem.MuxEnabled;
|
||||||
item.Cert = profileItem.Cert;
|
item.Cert = profileItem.Cert;
|
||||||
|
item.EchConfigList = profileItem.EchConfigList;
|
||||||
|
item.EchForceQuery = profileItem.EchForceQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = item.ConfigType switch
|
var ret = item.ConfigType switch
|
||||||
|
|
@ -1273,7 +1275,7 @@ public static class ConfigHandler
|
||||||
}
|
}
|
||||||
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
|
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0)
|
||||||
{
|
{
|
||||||
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||||
itemSocks = new ProfileItem()
|
itemSocks = new ProfileItem()
|
||||||
{
|
{
|
||||||
CoreType = preCoreType,
|
CoreType = preCoreType,
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,10 @@ public class BaseFmt
|
||||||
}
|
}
|
||||||
ToUriQueryAllowInsecure(item, ref dicQuery);
|
ToUriQueryAllowInsecure(item, ref dicQuery);
|
||||||
}
|
}
|
||||||
|
if (item.EchConfigList.IsNotEmpty())
|
||||||
|
{
|
||||||
|
dicQuery.Add("ech", Utils.UrlEncode(item.EchConfigList));
|
||||||
|
}
|
||||||
|
|
||||||
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
|
dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp));
|
||||||
|
|
||||||
|
|
@ -209,6 +213,7 @@ public class BaseFmt
|
||||||
item.ShortId = GetQueryDecoded(query, "sid");
|
item.ShortId = GetQueryDecoded(query, "sid");
|
||||||
item.SpiderX = GetQueryDecoded(query, "spx");
|
item.SpiderX = GetQueryDecoded(query, "spx");
|
||||||
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
||||||
|
item.EchConfigList = GetQueryDecoded(query, "ech");
|
||||||
|
|
||||||
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
|
if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1"))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,10 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
{
|
{
|
||||||
pluginArgs += "mode=websocket;";
|
pluginArgs += "mode=websocket;";
|
||||||
pluginArgs += $"host={item.RequestHost};";
|
pluginArgs += $"host={item.RequestHost};";
|
||||||
pluginArgs += $"path={item.Path};";
|
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||||
|
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||||
|
var path = item.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||||
|
pluginArgs += $"path={path};";
|
||||||
}
|
}
|
||||||
else if (item.Network == nameof(ETransport.quic))
|
else if (item.Network == nameof(ETransport.quic))
|
||||||
{
|
{
|
||||||
|
|
@ -75,8 +78,6 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
|
|
||||||
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
||||||
|
|
||||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
|
||||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
|
||||||
base64Content = base64Content.Replace("=", "\\=");
|
base64Content = base64Content.Replace("=", "\\=");
|
||||||
|
|
||||||
pluginArgs += $"certRaw={base64Content};";
|
pluginArgs += $"certRaw={base64Content};";
|
||||||
|
|
@ -85,6 +86,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
if (pluginArgs.Length > 0)
|
if (pluginArgs.Length > 0)
|
||||||
{
|
{
|
||||||
plugin = "v2ray-plugin";
|
plugin = "v2ray-plugin";
|
||||||
|
pluginArgs += "mux=0;";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,6 +224,7 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
|
var path = pluginParts.FirstOrDefault(t => t.StartsWith("path="));
|
||||||
var hasTls = pluginParts.Any(t => t == "tls");
|
var hasTls = pluginParts.Any(t => t == "tls");
|
||||||
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
|
var certRaw = pluginParts.FirstOrDefault(t => t.StartsWith("certRaw="));
|
||||||
|
var mux = pluginParts.FirstOrDefault(t => t.StartsWith("mux="));
|
||||||
|
|
||||||
var modeValue = mode.Replace("mode=", "");
|
var modeValue = mode.Replace("mode=", "");
|
||||||
if (modeValue == "websocket")
|
if (modeValue == "websocket")
|
||||||
|
|
@ -234,7 +237,9 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
}
|
}
|
||||||
if (!path.IsNullOrEmpty())
|
if (!path.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
item.Path = path.Replace("path=", "");
|
var pathValue = path.Replace("path=", "");
|
||||||
|
pathValue = pathValue.Replace("\\=", "=").Replace("\\,", ",").Replace("\\\\", "\\");
|
||||||
|
item.Path = pathValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (modeValue == "quic")
|
else if (modeValue == "quic")
|
||||||
|
|
@ -258,6 +263,16 @@ public class ShadowsocksFmt : BaseFmt
|
||||||
item.Cert = certPem;
|
item.Cert = certPem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mux.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var muxValue = mux.Replace("mux=", "");
|
||||||
|
var muxCount = muxValue.ToInt();
|
||||||
|
if (muxCount > 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,11 @@ namespace ServiceLib.Manager;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActionPrecheckManager(Config config)
|
public class ActionPrecheckManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<ActionPrecheckManager> _instance = new(() => new ActionPrecheckManager(AppManager.Instance.Config));
|
private static readonly Lazy<ActionPrecheckManager> _instance = new();
|
||||||
public static ActionPrecheckManager Instance => _instance.Value;
|
public static ActionPrecheckManager Instance => _instance.Value;
|
||||||
|
|
||||||
private readonly Config _config = config;
|
|
||||||
|
|
||||||
// sing-box supported transports for different protocol types
|
// sing-box supported transports for different protocol types
|
||||||
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
|
private static readonly HashSet<string> SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)];
|
||||||
|
|
||||||
|
|
@ -56,6 +54,7 @@ public class ActionPrecheckManager(Config config)
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||||
return await ValidateNodeAndCoreSupport(item, coreType);
|
return await ValidateNodeAndCoreSupport(item, coreType);
|
||||||
}
|
}
|
||||||
|
|
@ -71,21 +70,64 @@ public class ActionPrecheckManager(Config config)
|
||||||
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString()));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
else if (item.ConfigType.IsGroupType())
|
||||||
if (!item.IsComplex())
|
|
||||||
{
|
{
|
||||||
|
var groupErrors = await ValidateGroupNode(item, coreType);
|
||||||
|
errors.AddRange(groupErrors);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
else if (!item.IsComplex())
|
||||||
|
{
|
||||||
|
var normalErrors = await ValidateNormalNode(item, coreType);
|
||||||
|
errors.AddRange(normalErrors);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<string>> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
if (item.Address.IsNullOrEmpty())
|
if (item.Address.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "Address"));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Port is <= 0 or >= 65536)
|
if (item.Port is <= 0 or > 65535)
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "Port"));
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var net = item.GetNetwork();
|
||||||
|
|
||||||
|
if (coreType == ECoreType.sing_box)
|
||||||
|
{
|
||||||
|
var transportError = ValidateSingboxTransport(item.ConfigType, net);
|
||||||
|
if (transportError != null)
|
||||||
|
{
|
||||||
|
errors.Add(transportError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Global.SingboxSupportConfigType.Contains(item.ConfigType))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
|
||||||
|
nameof(ECoreType.sing_box), item.ConfigType.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (coreType is ECoreType.Xray)
|
||||||
|
{
|
||||||
|
// Xray core does not support these protocols
|
||||||
|
if (!Global.XraySupportConfigType.Contains(item.ConfigType))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.CoreNotSupportProtocol,
|
||||||
|
nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (item.ConfigType)
|
switch (item.ConfigType)
|
||||||
{
|
{
|
||||||
case EConfigType.VMess:
|
case EConfigType.VMess:
|
||||||
|
|
@ -123,21 +165,41 @@ public class ActionPrecheckManager(Config config)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ConfigType is EConfigType.VLESS or EConfigType.Trojan
|
if (item.StreamSecurity == Global.StreamSecurity)
|
||||||
&& item.StreamSecurity == Global.StreamSecurityReality
|
{
|
||||||
&& item.PublicKey.IsNullOrEmpty())
|
// check certificate validity
|
||||||
|
if ((!item.Cert.IsNullOrEmpty()) && (CertPemManager.ParsePemChain(item.Cert).Count == 0))
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.StreamSecurity == Global.StreamSecurityReality)
|
||||||
|
{
|
||||||
|
if (item.PublicKey.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.Count > 0)
|
if (item.Network == nameof(ETransport.xhttp)
|
||||||
|
&& !item.Extra.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
|
// check xhttp extra json validity
|
||||||
|
var xhttpExtra = JsonUtils.ParseJson(item.Extra);
|
||||||
|
if (xhttpExtra is null)
|
||||||
|
{
|
||||||
|
errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (item.ConfigType.IsGroupType())
|
private async Task<List<string>> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null)
|
||||||
{
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
|
||||||
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
ProfileGroupItemManager.Instance.TryGet(item.IndexId, out var group);
|
||||||
if (group is null || group.NotHasChild())
|
if (group is null || group.NotHasChild())
|
||||||
{
|
{
|
||||||
|
|
@ -152,9 +214,10 @@ public class ActionPrecheckManager(Config config)
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
var childIds = Utils.String2List(group.ChildItems) ?? [];
|
var childIds = new List<string>();
|
||||||
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
|
var subItems = await ProfileGroupItemManager.GetSubChildProfileItems(group);
|
||||||
childIds.AddRange(subItems.Select(p => p.IndexId));
|
childIds.AddRange(subItems.Select(p => p.IndexId));
|
||||||
|
childIds.AddRange(Utils.String2List(group.ChildItems));
|
||||||
|
|
||||||
foreach (var child in childIds)
|
foreach (var child in childIds)
|
||||||
{
|
{
|
||||||
|
|
@ -178,36 +241,11 @@ public class ActionPrecheckManager(Config config)
|
||||||
}
|
}
|
||||||
|
|
||||||
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType));
|
||||||
errors.AddRange(childErrors);
|
errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: ")));
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
var net = item.GetNetwork();
|
|
||||||
|
|
||||||
if (coreType == ECoreType.sing_box)
|
|
||||||
{
|
|
||||||
var transportError = ValidateSingboxTransport(item.ConfigType, net);
|
|
||||||
if (transportError != null)
|
|
||||||
{
|
|
||||||
errors.Add(transportError);
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (coreType is ECoreType.Xray)
|
|
||||||
{
|
|
||||||
// Xray core does not support these protocols
|
|
||||||
if (!Global.XraySupportConfigType.Contains(item.ConfigType)
|
|
||||||
&& !item.IsComplex())
|
|
||||||
{
|
|
||||||
errors.Add(string.Format(ResUI.CoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType.ToString()));
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? ValidateSingboxTransport(EConfigType configType, string net)
|
private static string? ValidateSingboxTransport(EConfigType configType, string net)
|
||||||
{
|
{
|
||||||
// sing-box does not support xhttp / kcp transports
|
// sing-box does not support xhttp / kcp transports
|
||||||
|
|
@ -271,7 +309,7 @@ public class ActionPrecheckManager(Config config)
|
||||||
if (node is not null)
|
if (node is not null)
|
||||||
{
|
{
|
||||||
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType);
|
||||||
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + s));
|
errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s));
|
||||||
}
|
}
|
||||||
else if (tag.IsNotEmpty())
|
else if (tag.IsNotEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -289,7 +327,7 @@ public class ActionPrecheckManager(Config config)
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType);
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config);
|
||||||
if (routing == null)
|
if (routing == null)
|
||||||
{
|
{
|
||||||
return errors;
|
return errors;
|
||||||
|
|
@ -317,7 +355,7 @@ public class ActionPrecheckManager(Config config)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType);
|
||||||
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + s));
|
errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s));
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,23 @@ public sealed class AppManager
|
||||||
|
|
||||||
public string LinuxSudoPwd { get; set; }
|
public string LinuxSudoPwd { get; set; }
|
||||||
|
|
||||||
|
public bool ShowInTaskbar { get; set; }
|
||||||
|
|
||||||
|
public ECoreType RunningCoreType { get; set; }
|
||||||
|
|
||||||
|
public bool IsRunningCore(ECoreType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
|
||||||
|
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Property
|
#endregion Property
|
||||||
|
|
||||||
#region App
|
#region App
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ public sealed class CoreInfoManager
|
||||||
new CoreInfo
|
new CoreInfo
|
||||||
{
|
{
|
||||||
CoreType = ECoreType.mihomo,
|
CoreType = ECoreType.mihomo,
|
||||||
CoreExes = ["mihomo-windows-amd64-v1", "mihomo-windows-amd64-compatible", "mihomo-windows-amd64", "mihomo-linux-amd64", "clash", "mihomo"],
|
CoreExes = GetMihomoCoreExes(),
|
||||||
Arguments = "-f {0}" + PortableMode(),
|
Arguments = "-f {0}" + PortableMode(),
|
||||||
Url = GetCoreUrl(ECoreType.mihomo),
|
Url = GetCoreUrl(ECoreType.mihomo),
|
||||||
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
ReleaseApiUrl = urlMihomo.Replace(Global.GithubUrl, Global.GithubApiUrl),
|
||||||
|
|
@ -248,4 +248,34 @@ public sealed class CoreInfoManager
|
||||||
{
|
{
|
||||||
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
|
return $"{Global.GithubUrl}/{Global.CoreUrls[eCoreType]}/releases";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<string>? GetMihomoCoreExes()
|
||||||
|
{
|
||||||
|
var names = new List<string>();
|
||||||
|
|
||||||
|
if (Utils.IsWindows())
|
||||||
|
{
|
||||||
|
names.Add("mihomo-windows-amd64-v1");
|
||||||
|
names.Add("mihomo-windows-amd64-compatible");
|
||||||
|
names.Add("mihomo-windows-amd64");
|
||||||
|
names.Add("mihomo-windows-arm64");
|
||||||
|
}
|
||||||
|
else if (Utils.IsLinux())
|
||||||
|
{
|
||||||
|
names.Add("mihomo-linux-amd64-v1");
|
||||||
|
names.Add("mihomo-linux-amd64");
|
||||||
|
names.Add("mihomo-linux-arm64");
|
||||||
|
}
|
||||||
|
else if (Utils.IsMacOS())
|
||||||
|
{
|
||||||
|
names.Add("mihomo-darwin-amd64-v1");
|
||||||
|
names.Add("mihomo-darwin-amd64");
|
||||||
|
names.Add("mihomo-darwin-arm64");
|
||||||
|
}
|
||||||
|
|
||||||
|
names.Add("clash");
|
||||||
|
names.Add("mihomo");
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ public class CoreManager
|
||||||
|
|
||||||
private async Task CoreStart(ProfileItem node)
|
private async Task CoreStart(ProfileItem node)
|
||||||
{
|
{
|
||||||
var coreType = _config.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
|
|
||||||
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
var displayLog = node.ConfigType != EConfigType.Custom || node.DisplayLog;
|
||||||
|
|
|
||||||
|
|
@ -230,9 +230,10 @@ public class ProfileGroupItemManager
|
||||||
{
|
{
|
||||||
return (new List<ProfileItem>(), profileGroupItem);
|
return (new List<ProfileItem>(), profileGroupItem);
|
||||||
}
|
}
|
||||||
var items = await GetChildProfileItems(profileGroupItem);
|
|
||||||
var subItems = await GetSubChildProfileItems(profileGroupItem);
|
var items = new List<ProfileItem>();
|
||||||
items.AddRange(subItems);
|
items.AddRange(await GetSubChildProfileItems(profileGroupItem));
|
||||||
|
items.AddRange(await GetChildProfileItems(profileGroupItem));
|
||||||
|
|
||||||
return (items, profileGroupItem);
|
return (items, profileGroupItem);
|
||||||
}
|
}
|
||||||
|
|
@ -316,5 +317,72 @@ public class ProfileGroupItemManager
|
||||||
return childAddresses;
|
return childAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<HashSet<string>> GetAllChildEchQuerySni(string indexId)
|
||||||
|
{
|
||||||
|
// include grand children
|
||||||
|
var childAddresses = new HashSet<string>();
|
||||||
|
if (!Instance.TryGet(indexId, out var groupItem) || groupItem == null)
|
||||||
|
{
|
||||||
|
return childAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupItem.SubChildItems.IsNotEmpty())
|
||||||
|
{
|
||||||
|
var subItems = await GetSubChildProfileItems(groupItem);
|
||||||
|
foreach (var childNode in subItems)
|
||||||
|
{
|
||||||
|
if (childNode.EchConfigList.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (childNode.StreamSecurity == Global.StreamSecurity
|
||||||
|
&& childNode.EchConfigList?.Contains("://") == true)
|
||||||
|
{
|
||||||
|
var idx = childNode.EchConfigList.IndexOf('+');
|
||||||
|
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
childAddresses.Add(childNode.Sni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var childIds = Utils.String2List(groupItem.ChildItems) ?? [];
|
||||||
|
|
||||||
|
foreach (var childId in childIds)
|
||||||
|
{
|
||||||
|
var childNode = await AppManager.Instance.GetProfileItem(childId);
|
||||||
|
if (childNode == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
if (childNode.StreamSecurity == Global.StreamSecurity
|
||||||
|
&& childNode.EchConfigList?.Contains("://") == true)
|
||||||
|
{
|
||||||
|
var idx = childNode.EchConfigList.IndexOf('+');
|
||||||
|
childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
childAddresses.Add(childNode.Sni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (childNode.ConfigType.IsGroupType())
|
||||||
|
{
|
||||||
|
var subAddresses = await GetAllChildDomainAddresses(childNode.IndexId);
|
||||||
|
foreach (var addr in subAddresses)
|
||||||
|
{
|
||||||
|
childAddresses.Add(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return childAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Helper
|
#endregion Helper
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,6 @@ public class Config
|
||||||
public string IndexId { get; set; }
|
public string IndexId { get; set; }
|
||||||
public string SubIndexId { get; set; }
|
public string SubIndexId { get; set; }
|
||||||
|
|
||||||
public ECoreType RunningCoreType { get; set; }
|
|
||||||
|
|
||||||
public bool IsRunningCore(ECoreType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ECoreType.Xray when RunningCoreType is ECoreType.Xray or ECoreType.v2fly or ECoreType.v2fly_v5:
|
|
||||||
case ECoreType.sing_box when RunningCoreType is ECoreType.sing_box or ECoreType.mihomo:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion property
|
#endregion property
|
||||||
|
|
||||||
#region other entities
|
#region other entities
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ public class UIItem
|
||||||
public bool DoubleClick2Activate { get; set; }
|
public bool DoubleClick2Activate { get; set; }
|
||||||
public bool AutoHideStartup { get; set; }
|
public bool AutoHideStartup { get; set; }
|
||||||
public bool Hide2TrayWhenClose { get; set; }
|
public bool Hide2TrayWhenClose { get; set; }
|
||||||
public bool ShowInTaskbar { get; set; }
|
|
||||||
public bool MacOSShowInDock { get; set; }
|
public bool MacOSShowInDock { get; set; }
|
||||||
public List<ColumnItem> MainColumnItem { get; set; }
|
public List<ColumnItem> MainColumnItem { get; set; }
|
||||||
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
public List<WindowSizeItem> WindowSizeItem { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -161,4 +161,6 @@ public class ProfileItem : ReactiveObject
|
||||||
public string Extra { get; set; }
|
public string Extra { get; set; }
|
||||||
public bool? MuxEnabled { get; set; }
|
public bool? MuxEnabled { get; set; }
|
||||||
public string Cert { get; set; }
|
public string Cert { get; set; }
|
||||||
|
public string EchConfigList { get; set; }
|
||||||
|
public string EchForceQuery { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,14 @@ public class Tls4Sbox
|
||||||
public string? fragment_fallback_delay { get; set; }
|
public string? fragment_fallback_delay { get; set; }
|
||||||
public bool? record_fragment { get; set; }
|
public bool? record_fragment { get; set; }
|
||||||
public List<string>? certificate { get; set; }
|
public List<string>? certificate { get; set; }
|
||||||
|
public Ech4Sbox? ech { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Ech4Sbox
|
||||||
|
{
|
||||||
|
public bool enabled { get; set; }
|
||||||
|
public List<string>? config { get; set; }
|
||||||
|
public string? query_server_name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Multiplex4Sbox
|
public class Multiplex4Sbox
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,8 @@ public class TlsSettings4Ray
|
||||||
public string? mldsa65Verify { get; set; }
|
public string? mldsa65Verify { get; set; }
|
||||||
public List<CertificateSettings4Ray>? certificates { get; set; }
|
public List<CertificateSettings4Ray>? certificates { get; set; }
|
||||||
public bool? disableSystemRoot { get; set; }
|
public bool? disableSystemRoot { get; set; }
|
||||||
|
public string? echConfigList { get; set; }
|
||||||
|
public string? echForceQuery { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CertificateSettings4Ray
|
public class CertificateSettings4Ray
|
||||||
|
|
|
||||||
20
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
20
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
|
@ -19,7 +19,7 @@ namespace ServiceLib.Resx {
|
||||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
public class ResUI {
|
public class ResUI {
|
||||||
|
|
@ -2790,6 +2790,24 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 EchConfigList 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbEchConfigList {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbEchConfigList", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 EchForceQuery 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbEchForceQuery {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbEchForceQuery", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Edit 的本地化字符串。
|
/// 查找类似 Edit 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1636,6 +1636,12 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
|
||||||
<value>Afficher dans le Dock de macOS (redém. requis)</value>
|
<value>Afficher dans le Dock de macOS (redém. requis)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1641,4 +1641,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>Configuration Item 2, Select and add from self-built</value>
|
<value>Configuration Item 2, Select and add from self-built</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1638,4 +1638,10 @@
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>子配置项二,从自建中选择添加</value>
|
<value>子配置项二,从自建中选择添加</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1638,4 +1638,10 @@
|
||||||
<data name="menuServerList2" xml:space="preserve">
|
<data name="menuServerList2" xml:space="preserve">
|
||||||
<value>子配置項二,從自建中選擇新增</value>
|
<value>子配置項二,從自建中選擇新增</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbEchConfigList" xml:space="preserve">
|
||||||
|
<value>EchConfigList</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbEchForceQuery" xml:space="preserve">
|
||||||
|
<value>EchForceQuery</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -371,7 +371,7 @@ public partial class CoreConfigSingboxService(Config config)
|
||||||
|
|
||||||
await GenRouting(singboxConfig);
|
await GenRouting(singboxConfig);
|
||||||
await GenExperimental(singboxConfig);
|
await GenExperimental(singboxConfig);
|
||||||
await GenDns(null, singboxConfig);
|
await GenDns(parentNode, singboxConfig);
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
await ConvertGeo2Ruleset(singboxConfig);
|
||||||
|
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|
@ -428,7 +428,7 @@ public partial class CoreConfigSingboxService(Config config)
|
||||||
|
|
||||||
await GenRouting(singboxConfig);
|
await GenRouting(singboxConfig);
|
||||||
await GenExperimental(singboxConfig);
|
await GenExperimental(singboxConfig);
|
||||||
await GenDns(null, singboxConfig);
|
await GenDns(parentNode, singboxConfig);
|
||||||
await ConvertGeo2Ruleset(singboxConfig);
|
await ConvertGeo2Ruleset(singboxConfig);
|
||||||
|
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ public partial class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
|
|
||||||
var simpleDNSItem = _config.SimpleDNSItem;
|
var simpleDNSItem = _config.SimpleDNSItem;
|
||||||
await GenDnsServers(singboxConfig, simpleDNSItem);
|
await GenDnsServers(node, singboxConfig, simpleDNSItem);
|
||||||
await GenDnsRules(singboxConfig, simpleDNSItem);
|
await GenDnsRules(node, singboxConfig, simpleDNSItem);
|
||||||
|
|
||||||
singboxConfig.dns ??= new Dns4Sbox();
|
singboxConfig.dns ??= new Dns4Sbox();
|
||||||
singboxConfig.dns.independent_cache = true;
|
singboxConfig.dns.independent_cache = true;
|
||||||
|
|
@ -52,7 +52,7 @@ public partial class CoreConfigSingboxService
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsServers(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
private async Task<int> GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
||||||
{
|
{
|
||||||
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
|
var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem);
|
||||||
|
|
||||||
|
|
@ -133,6 +133,29 @@ public partial class CoreConfigSingboxService
|
||||||
singboxConfig.dns.servers.Add(fakeip);
|
singboxConfig.dns.servers.Add(fakeip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ech
|
||||||
|
var (_, dnsServer) = ParseEchParam(node?.EchConfigList);
|
||||||
|
if (dnsServer is not null)
|
||||||
|
{
|
||||||
|
dnsServer.tag = Global.SingboxEchDNSTag;
|
||||||
|
if (dnsServer.server is not null
|
||||||
|
&& hostsDns.predefined.ContainsKey(dnsServer.server))
|
||||||
|
{
|
||||||
|
dnsServer.domain_resolver = Global.SingboxHostsDNSTag;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dnsServer.domain_resolver = Global.SingboxLocalDNSTag;
|
||||||
|
}
|
||||||
|
singboxConfig.dns.servers.Add(dnsServer);
|
||||||
|
}
|
||||||
|
else if (node?.ConfigType.IsGroupType() == true)
|
||||||
|
{
|
||||||
|
var echDnsObject = JsonUtils.DeepCopy(directDns);
|
||||||
|
echDnsObject.tag = Global.SingboxEchDNSTag;
|
||||||
|
singboxConfig.dns.servers.Add(echDnsObject);
|
||||||
|
}
|
||||||
|
|
||||||
return await Task.FromResult(0);
|
return await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +169,7 @@ public partial class CoreConfigSingboxService
|
||||||
return await Task.FromResult(finalDns);
|
return await Task.FromResult(finalDns);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> GenDnsRules(SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
private async Task<int> GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem)
|
||||||
{
|
{
|
||||||
singboxConfig.dns ??= new Dns4Sbox();
|
singboxConfig.dns ??= new Dns4Sbox();
|
||||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
||||||
|
|
@ -157,17 +180,42 @@ public partial class CoreConfigSingboxService
|
||||||
new Rule4Sbox
|
new Rule4Sbox
|
||||||
{
|
{
|
||||||
server = Global.SingboxRemoteDNSTag,
|
server = Global.SingboxRemoteDNSTag,
|
||||||
strategy = simpleDNSItem.SingboxStrategy4Proxy.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Proxy,
|
strategy = simpleDNSItem.SingboxStrategy4Proxy.NullIfEmpty(),
|
||||||
clash_mode = ERuleMode.Global.ToString()
|
clash_mode = ERuleMode.Global.ToString()
|
||||||
},
|
},
|
||||||
new Rule4Sbox
|
new Rule4Sbox
|
||||||
{
|
{
|
||||||
server = Global.SingboxDirectDNSTag,
|
server = Global.SingboxDirectDNSTag,
|
||||||
strategy = simpleDNSItem.SingboxStrategy4Direct.IsNullOrEmpty() ? null : simpleDNSItem.SingboxStrategy4Direct,
|
strategy = simpleDNSItem.SingboxStrategy4Direct.NullIfEmpty(),
|
||||||
clash_mode = ERuleMode.Direct.ToString()
|
clash_mode = ERuleMode.Direct.ToString()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var (ech, _) = ParseEchParam(node?.EchConfigList);
|
||||||
|
if (ech is not null)
|
||||||
|
{
|
||||||
|
var echDomain = ech.query_server_name ?? node?.Sni;
|
||||||
|
singboxConfig.dns.rules.Add(new()
|
||||||
|
{
|
||||||
|
query_type = new List<int> { 64, 65 },
|
||||||
|
server = Global.SingboxEchDNSTag,
|
||||||
|
domain = echDomain is not null ? new List<string> { echDomain } : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (node?.ConfigType.IsGroupType() == true)
|
||||||
|
{
|
||||||
|
var queryServerNames = (await ProfileGroupItemManager.GetAllChildEchQuerySni(node.IndexId)).ToList();
|
||||||
|
if (queryServerNames.Count > 0)
|
||||||
|
{
|
||||||
|
singboxConfig.dns.rules.Add(new()
|
||||||
|
{
|
||||||
|
query_type = new List<int> { 64, 65 },
|
||||||
|
server = Global.SingboxEchDNSTag,
|
||||||
|
domain = queryServerNames,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (simpleDNSItem.BlockBindingQuery == true)
|
if (simpleDNSItem.BlockBindingQuery == true)
|
||||||
{
|
{
|
||||||
singboxConfig.dns.rules.Add(new()
|
singboxConfig.dns.rules.Add(new()
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ public partial class CoreConfigSingboxService
|
||||||
singboxConfig.inbounds = [];
|
singboxConfig.inbounds = [];
|
||||||
|
|
||||||
if (!_config.TunModeItem.EnableTun
|
if (!_config.TunModeItem.EnableTun
|
||||||
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && _config.RunningCoreType == ECoreType.sing_box))
|
|| (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box))
|
||||||
{
|
{
|
||||||
var inbound = new Inbound4Sbox()
|
var inbound = new Inbound4Sbox()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,10 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
pluginArgs += "mode=websocket;";
|
pluginArgs += "mode=websocket;";
|
||||||
pluginArgs += $"host={node.RequestHost};";
|
pluginArgs += $"host={node.RequestHost};";
|
||||||
pluginArgs += $"path={node.Path};";
|
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
||||||
|
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
||||||
|
var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,");
|
||||||
|
pluginArgs += $"path={path};";
|
||||||
}
|
}
|
||||||
else if (node.Network == nameof(ETransport.quic))
|
else if (node.Network == nameof(ETransport.quic))
|
||||||
{
|
{
|
||||||
|
|
@ -64,8 +67,6 @@ public partial class CoreConfigSingboxService
|
||||||
|
|
||||||
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
var base64Content = cert.Replace(beginMarker, "").Replace(endMarker, "").Trim();
|
||||||
|
|
||||||
// https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172
|
|
||||||
// Equal signs and commas [and backslashes] must be escaped with a backslash.
|
|
||||||
base64Content = base64Content.Replace("=", "\\=");
|
base64Content = base64Content.Replace("=", "\\=");
|
||||||
|
|
||||||
pluginArgs += $"certRaw={base64Content};";
|
pluginArgs += $"certRaw={base64Content};";
|
||||||
|
|
@ -74,6 +75,9 @@ public partial class CoreConfigSingboxService
|
||||||
if (pluginArgs.Length > 0)
|
if (pluginArgs.Length > 0)
|
||||||
{
|
{
|
||||||
outbound.plugin = "v2ray-plugin";
|
outbound.plugin = "v2ray-plugin";
|
||||||
|
pluginArgs += "mux=0;";
|
||||||
|
// pluginStr remove last ';'
|
||||||
|
pluginArgs = pluginArgs[..^1];
|
||||||
outbound.plugin_opts = pluginArgs;
|
outbound.plugin_opts = pluginArgs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -330,6 +334,11 @@ public partial class CoreConfigSingboxService
|
||||||
};
|
};
|
||||||
tls.insecure = false;
|
tls.insecure = false;
|
||||||
}
|
}
|
||||||
|
var (ech, _) = ParseEchParam(node.EchConfigList);
|
||||||
|
if (ech is not null)
|
||||||
|
{
|
||||||
|
tls.ech = ech;
|
||||||
|
}
|
||||||
outbound.tls = tls;
|
outbound.tls = tls;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -350,7 +359,7 @@ public partial class CoreConfigSingboxService
|
||||||
case nameof(ETransport.h2):
|
case nameof(ETransport.h2):
|
||||||
transport.type = nameof(ETransport.http);
|
transport.type = nameof(ETransport.http);
|
||||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
transport.path = node.Path.NullIfEmpty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.tcp): //http
|
case nameof(ETransport.tcp): //http
|
||||||
|
|
@ -358,7 +367,7 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
transport.type = nameof(ETransport.http);
|
transport.type = nameof(ETransport.http);
|
||||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost);
|
||||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
transport.path = node.Path.NullIfEmpty();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -392,7 +401,7 @@ public partial class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transport.path = wsPath.IsNullOrEmpty() ? null : wsPath;
|
transport.path = wsPath.NullIfEmpty();
|
||||||
if (node.RequestHost.IsNotEmpty())
|
if (node.RequestHost.IsNotEmpty())
|
||||||
{
|
{
|
||||||
transport.headers = new()
|
transport.headers = new()
|
||||||
|
|
@ -404,8 +413,8 @@ public partial class CoreConfigSingboxService
|
||||||
|
|
||||||
case nameof(ETransport.httpupgrade):
|
case nameof(ETransport.httpupgrade):
|
||||||
transport.type = nameof(ETransport.httpupgrade);
|
transport.type = nameof(ETransport.httpupgrade);
|
||||||
transport.path = node.Path.IsNullOrEmpty() ? null : node.Path;
|
transport.path = node.Path.NullIfEmpty();
|
||||||
transport.host = node.RequestHost.IsNullOrEmpty() ? null : node.RequestHost;
|
transport.host = node.RequestHost.NullIfEmpty();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -900,4 +909,31 @@ public partial class CoreConfigSingboxService
|
||||||
}
|
}
|
||||||
return await Task.FromResult(0);
|
return await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig)
|
||||||
|
{
|
||||||
|
if (echConfig.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
if (!echConfig.Contains("://"))
|
||||||
|
{
|
||||||
|
return (new Ech4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
config = [$"-----BEGIN ECH CONFIGS-----\n" +
|
||||||
|
$"{echConfig}\n" +
|
||||||
|
$"-----END ECH CONFIGS-----"],
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
var idx = echConfig.IndexOf('+');
|
||||||
|
// NOTE: query_server_name, since sing-box 1.13.0
|
||||||
|
//var queryServerName = idx > 0 ? echConfig[..idx] : null;
|
||||||
|
var echDnsServer = idx > 0 ? echConfig[(idx + 1)..] : echConfig;
|
||||||
|
return (new Ech4Sbox()
|
||||||
|
{
|
||||||
|
enabled = true,
|
||||||
|
query_server_name = null,
|
||||||
|
}, ParseDnsAddress(echDnsServer));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ public partial class CoreConfigSingboxService
|
||||||
clash_mode = ERuleMode.Global.ToString()
|
clash_mode = ERuleMode.Global.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.IsNullOrEmpty() ? null : _config.RoutingBasicItem.DomainStrategy4Singbox;
|
var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty();
|
||||||
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
|
var defaultRouting = await ConfigHandler.GetDefaultRouting(_config);
|
||||||
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
|
if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ public partial class CoreConfigV2rayService
|
||||||
|
|
||||||
if (item.OutboundTag == Global.DirectTag)
|
if (item.OutboundTag == Global.DirectTag)
|
||||||
{
|
{
|
||||||
if (normalizedDomain.StartsWith("geosite:"))
|
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
|
||||||
{
|
{
|
||||||
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +208,7 @@ public partial class CoreConfigV2rayService
|
||||||
}
|
}
|
||||||
else if (item.OutboundTag != Global.BlockTag)
|
else if (item.OutboundTag != Global.BlockTag)
|
||||||
{
|
{
|
||||||
if (normalizedDomain.StartsWith("geosite:"))
|
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
|
||||||
{
|
{
|
||||||
proxyGeositeList.Add(normalizedDomain);
|
proxyGeositeList.Add(normalizedDomain);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,9 @@ public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure),
|
||||||
alpn = node.GetAlpn(),
|
alpn = node.GetAlpn(),
|
||||||
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint
|
fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint,
|
||||||
|
echConfigList = node.EchConfigList.NullIfEmpty(),
|
||||||
|
echForceQuery = node.EchForceQuery.NullIfEmpty()
|
||||||
};
|
};
|
||||||
if (sni.IsNotEmpty())
|
if (sni.IsNotEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -340,7 +342,7 @@ public partial class CoreConfigV2rayService
|
||||||
kcpSettings.header = new Header4Ray
|
kcpSettings.header = new Header4Ray
|
||||||
{
|
{
|
||||||
type = node.HeaderType,
|
type = node.HeaderType,
|
||||||
domain = host.IsNullOrEmpty() ? null : host
|
domain = host.NullIfEmpty()
|
||||||
};
|
};
|
||||||
if (path.IsNotEmpty())
|
if (path.IsNotEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -450,7 +452,7 @@ public partial class CoreConfigV2rayService
|
||||||
case nameof(ETransport.grpc):
|
case nameof(ETransport.grpc):
|
||||||
GrpcSettings4Ray grpcSettings = new()
|
GrpcSettings4Ray grpcSettings = new()
|
||||||
{
|
{
|
||||||
authority = host.IsNullOrEmpty() ? null : host,
|
authority = host.NullIfEmpty(),
|
||||||
serviceName = path,
|
serviceName = path,
|
||||||
multiMode = node.HeaderType == Global.GrpcMultiMode,
|
multiMode = node.HeaderType == Global.GrpcMultiMode,
|
||||||
idle_timeout = _config.GrpcItem.IdleTimeout,
|
idle_timeout = _config.GrpcItem.IdleTimeout,
|
||||||
|
|
@ -564,7 +566,7 @@ public partial class CoreConfigV2rayService
|
||||||
var fragmentOutbound = new Outbounds4Ray
|
var fragmentOutbound = new Outbounds4Ray
|
||||||
{
|
{
|
||||||
protocol = "freedom",
|
protocol = "freedom",
|
||||||
tag = $"{Global.ProxyTag}3",
|
tag = $"frag-{Global.ProxyTag}",
|
||||||
settings = new()
|
settings = new()
|
||||||
{
|
{
|
||||||
fragment = new()
|
fragment = new()
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
||||||
{
|
{
|
||||||
if (!it.AllowTest)
|
if (!it.AllowTest)
|
||||||
{
|
{
|
||||||
|
await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ public class StatisticsSingboxService
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_config.IsRunningCore(ECoreType.sing_box))
|
if (!AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ public class StatisticsXrayService
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config.RunningCoreType != ECoreType.Xray)
|
if (AppManager.Instance.RunningCoreType != ECoreType.Xray)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -291,13 +291,6 @@ public class UpdateService(Config config, Func<bool, string, Task> updateFunc)
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check for standalone windows .Net version
|
|
||||||
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "wpfgfx_cor3.dll"))
|
|
||||||
&& File.Exists(Path.Combine(Utils.GetBaseDirectory(), "D3DCompiler_47_cor3.dll")))
|
|
||||||
{
|
|
||||||
return url?.Replace(".zip", "-SelfContained.zip");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for avalonia desktop windows version
|
//Check for avalonia desktop windows version
|
||||||
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
|
if (File.Exists(Path.Combine(Utils.GetBaseDirectory(), "libHarfBuzzSharp.dll")))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ public class ClashConnectionsViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
await Task.Delay(1000 * 5);
|
await Task.Delay(1000 * 5);
|
||||||
numOfExecuted++;
|
numOfExecuted++;
|
||||||
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
|
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -437,7 +437,7 @@ public class ClashProxiesViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
await Task.Delay(1000 * 60);
|
await Task.Delay(1000 * 60);
|
||||||
numOfExecuted++;
|
numOfExecuted++;
|
||||||
if (!(AutoRefresh && _config.UiItem.ShowInTaskbar && _config.IsRunningCore(ECoreType.sing_box)))
|
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar && AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||||
|
|
||||||
private async Task Init()
|
private async Task Init()
|
||||||
{
|
{
|
||||||
_config.UiItem.ShowInTaskbar = true;
|
AppManager.Instance.ShowInTaskbar = true;
|
||||||
|
|
||||||
//await ConfigHandler.InitBuiltinRouting(_config);
|
//await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
await ConfigHandler.InitBuiltinDNS(_config);
|
await ConfigHandler.InitBuiltinDNS(_config);
|
||||||
|
|
@ -306,7 +306,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||||
|
|
||||||
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
|
private async Task UpdateStatisticsHandler(ServerSpeedItem update)
|
||||||
{
|
{
|
||||||
if (!_config.UiItem.ShowInTaskbar)
|
if (!AppManager.Instance.ShowInTaskbar)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -560,7 +560,7 @@ public class MainWindowViewModel : MyReactiveObject
|
||||||
});
|
});
|
||||||
AppEvents.TestServerRequested.Publish();
|
AppEvents.TestServerRequested.Publish();
|
||||||
|
|
||||||
var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
|
var showClashUI = AppManager.Instance.IsRunningCore(ECoreType.sing_box);
|
||||||
if (showClashUI)
|
if (showClashUI)
|
||||||
{
|
{
|
||||||
AppEvents.ProxiesReloadRequested.Publish();
|
AppEvents.ProxiesReloadRequested.Publish();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
|
||||||
|
|
||||||
EnqueueQueueMsg(msg);
|
EnqueueQueueMsg(msg);
|
||||||
|
|
||||||
if (!_config.UiItem.ShowInTaskbar)
|
if (!AppManager.Instance.ShowInTaskbar)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ public class ProfilesViewModel : MyReactiveObject
|
||||||
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
public ReactiveCommand<Unit, Unit> MoveUpCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
public ReactiveCommand<Unit, Unit> MoveDownCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
public ReactiveCommand<Unit, Unit> MoveBottomCmd { get; }
|
||||||
|
public ReactiveCommand<SubItem, Unit> MoveToGroupCmd { get; }
|
||||||
|
|
||||||
//servers ping
|
//servers ping
|
||||||
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
|
public ReactiveCommand<Unit, Unit> MixedTestServerCmd { get; }
|
||||||
|
|
@ -179,6 +180,10 @@ public class ProfilesViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
await MoveServer(EMove.Bottom);
|
await MoveServer(EMove.Bottom);
|
||||||
}, canEditRemove);
|
}, canEditRemove);
|
||||||
|
MoveToGroupCmd = ReactiveCommand.CreateFromTask<SubItem>(async sub =>
|
||||||
|
{
|
||||||
|
SelectedMoveToGroup = sub;
|
||||||
|
});
|
||||||
|
|
||||||
//servers ping
|
//servers ping
|
||||||
FastRealPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
FastRealPingCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
|
|
|
||||||
|
|
@ -549,7 +549,7 @@ public class StatusBarViewModel : MyReactiveObject
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_config.IsRunningCore(ECoreType.sing_box))
|
if (AppManager.Instance.IsRunningCore(ECoreType.sing_box))
|
||||||
{
|
{
|
||||||
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
|
SpeedProxyDisplay = string.Format(ResUI.SpeedDisplayText, EInboundProtocol.mixed, Utils.HumanFy(update.ProxyUp), Utils.HumanFy(update.ProxyDown));
|
||||||
SpeedDirectDisplay = string.Empty;
|
SpeedDirectDisplay = string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -713,7 +713,7 @@
|
||||||
Grid.Row="7"
|
Grid.Row="7"
|
||||||
ColumnDefinitions="300,Auto"
|
ColumnDefinitions="300,Auto"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
|
@ -768,15 +768,41 @@
|
||||||
Width="200"
|
Width="200"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}" />
|
||||||
|
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbEchConfigList}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtEchConfigList"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="400"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Left" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="cmbEchForceQuery"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="200"
|
||||||
|
Margin="{StaticResource Margin4}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbCertPinning}" />
|
Text="{x:Static resx:ResUI.TbCertPinning}" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="5"
|
Grid.Row="7"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||||
cmbFingerprint2.ItemsSource = Global.Fingerprints;
|
cmbFingerprint2.ItemsSource = Global.Fingerprints;
|
||||||
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
|
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
|
||||||
cmbAlpn.ItemsSource = Global.Alpns;
|
cmbAlpn.ItemsSource = Global.Alpns;
|
||||||
|
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
|
||||||
|
|
||||||
var lstStreamSecurity = new List<string>();
|
var lstStreamSecurity = new List<string>();
|
||||||
lstStreamSecurity.Add(string.Empty);
|
lstStreamSecurity.Add(string.Empty);
|
||||||
|
|
@ -187,6 +188,9 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.SelectedValue).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.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.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.EchForceQuery, v => v.cmbEchForceQuery.SelectedValue).DisposeWith(disposables);
|
||||||
|
|
||||||
//reality
|
//reality
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
|
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.Fingerprint, v => v.cmbFingerprint2.SelectedValue).DisposeWith(disposables);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
Title="v2rayN"
|
Title="v2rayN"
|
||||||
Width="1200"
|
Width="1200"
|
||||||
Height="800"
|
Height="800"
|
||||||
MinWidth="900"
|
MinWidth="600"
|
||||||
x:DataType="vms:MainWindowViewModel"
|
x:DataType="vms:MainWindowViewModel"
|
||||||
Icon="/Assets/NotifyIcon1.ico"
|
Icon="/Assets/NotifyIcon1.ico"
|
||||||
ShowInTaskbar="True"
|
ShowInTaskbar="True"
|
||||||
|
|
|
||||||
|
|
@ -409,8 +409,8 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||||
{
|
{
|
||||||
var bl = blShow ??
|
var bl = blShow ??
|
||||||
(Utils.IsLinux()
|
(Utils.IsLinux()
|
||||||
? (!_config.UiItem.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
|
? (!AppManager.Instance.ShowInTaskbar ^ (WindowState == WindowState.Minimized))
|
||||||
: !_config.UiItem.ShowInTaskbar);
|
: !AppManager.Instance.ShowInTaskbar);
|
||||||
if (bl)
|
if (bl)
|
||||||
{
|
{
|
||||||
Show();
|
Show();
|
||||||
|
|
@ -436,7 +436,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
_config.UiItem.ShowInTaskbar = bl;
|
AppManager.Instance.ShowInTaskbar = bl;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnLoaded(object? sender, RoutedEventArgs e)
|
protected override void OnLoaded(object? sender, RoutedEventArgs e)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||||
|
x:Name="Root"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
x:DataType="vms:ProfilesViewModel"
|
x:DataType="vms:ProfilesViewModel"
|
||||||
|
|
@ -141,19 +142,18 @@
|
||||||
InputGesture="Ctrl+T" />
|
InputGesture="Ctrl+T" />
|
||||||
<MenuItem x:Name="menuSortServerResult" Header="{x:Static resx:ResUI.menuSortServerResult}" />
|
<MenuItem x:Name="menuSortServerResult" Header="{x:Static resx:ResUI.menuSortServerResult}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem x:Name="menuMoveToGroup" Header="{x:Static resx:ResUI.menuMoveToGroup}">
|
<MenuItem
|
||||||
<MenuItem>
|
x:Name="menuMoveToGroup"
|
||||||
<MenuItem.Header>
|
Header="{x:Static resx:ResUI.menuMoveToGroup}"
|
||||||
<DockPanel>
|
ItemsSource="{Binding DataContext.SubItems, ElementName=Root}">
|
||||||
<ComboBox
|
<MenuItem.ItemTemplate>
|
||||||
x:Name="cmbMoveToGroup"
|
<DataTemplate>
|
||||||
Width="200"
|
<MenuItem
|
||||||
DisplayMemberBinding="{Binding Remarks}"
|
Command="{Binding DataContext.MoveToGroupCmd, ElementName=Root}"
|
||||||
ItemsSource="{Binding SubItems}"
|
CommandParameter="{Binding}"
|
||||||
ToolTip.Tip="{x:Static resx:ResUI.menuSubscription}" />
|
Header="{Binding Remarks}" />
|
||||||
</DockPanel>
|
</DataTemplate>
|
||||||
</MenuItem.Header>
|
</MenuItem.ItemTemplate>
|
||||||
</MenuItem>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="{x:Static resx:ResUI.menuMoveTo}">
|
<MenuItem Header="{x:Static resx:ResUI.menuMoveTo}">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
||||||
|
|
||||||
//servers move
|
//servers move
|
||||||
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
|
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
|
//this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).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.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,14 @@
|
||||||
<DataGrid.KeyBindings>
|
<DataGrid.KeyBindings>
|
||||||
<KeyBinding Command="{Binding SubDeleteCmd}" Gesture="Delete" />
|
<KeyBinding Command="{Binding SubDeleteCmd}" Gesture="Delete" />
|
||||||
</DataGrid.KeyBindings>
|
</DataGrid.KeyBindings>
|
||||||
|
<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem x:Name="menuSubAdd2" Header="{x:Static resx:ResUI.menuSubAdd}" />
|
||||||
|
<MenuItem x:Name="menuSubDelete2" Header="{x:Static resx:ResUI.menuSubDelete}" />
|
||||||
|
<MenuItem x:Name="menuSubEdit2" Header="{x:Static resx:ResUI.menuSubEdit}" />
|
||||||
|
<MenuItem x:Name="menuSubShare2" Header="{x:Static resx:ResUI.menuSubShare}" />
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn
|
<DataGridTextColumn
|
||||||
Width="*"
|
Width="*"
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ public partial class SubSettingWindow : WindowBase<SubSettingViewModel>
|
||||||
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd2).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete2).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit2).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare2).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
SecondaryColor="Lime" />
|
SecondaryColor="Lime" />
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
<system:Double x:Key="MenuItemHeight">26</system:Double>
|
<system:Double x:Key="MenuItemHeight">32</system:Double>
|
||||||
<system:Double x:Key="StdFontSize">12</system:Double>
|
<system:Double x:Key="StdFontSize">12</system:Double>
|
||||||
<system:Double x:Key="StdFontSize1">13</system:Double>
|
<system:Double x:Key="StdFontSize1">13</system:Double>
|
||||||
<system:Double x:Key="StdFontSize-1">11</system:Double>
|
<system:Double x:Key="StdFontSize-1">11</system:Double>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using MaterialDesignColors;
|
using MaterialDesignColors;
|
||||||
using MaterialDesignColors.ColorManipulation;
|
using MaterialDesignColors.ColorManipulation;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace v2rayN.ViewModels;
|
namespace v2rayN.ViewModels;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ public class ThemeSettingViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
_config = AppManager.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
|
|
||||||
RegisterSystemColorSet(_config, Application.Current.MainWindow, ModifyTheme);
|
RegisterSystemColorSet(_config, ModifyTheme);
|
||||||
|
|
||||||
BindingUI();
|
BindingUI();
|
||||||
RestoreUI();
|
RestoreUI();
|
||||||
|
|
@ -158,25 +159,15 @@ public class ThemeSettingViewModel : MyReactiveObject
|
||||||
_paletteHelper.SetTheme(theme);
|
_paletteHelper.SetTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterSystemColorSet(Config config, Window window, Action updateFunc)
|
public static void RegisterSystemColorSet(Config config, Action updateFunc)
|
||||||
{
|
{
|
||||||
var helper = new WindowInteropHelper(window);
|
SystemEvents.UserPreferenceChanged += (s, e) =>
|
||||||
var hwndSource = HwndSource.FromHwnd(helper.EnsureHandle());
|
|
||||||
hwndSource.AddHook((IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
|
|
||||||
{
|
{
|
||||||
if (config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem))
|
if ((e.Category == UserPreferenceCategory.Color || e.Category == UserPreferenceCategory.General)
|
||||||
{
|
&& config.UiItem.CurrentTheme == nameof(ETheme.FollowSystem))
|
||||||
const int WM_SETTINGCHANGE = 0x001A;
|
|
||||||
if (msg == WM_SETTINGCHANGE)
|
|
||||||
{
|
|
||||||
if (wParam == IntPtr.Zero && Marshal.PtrToStringUni(lParam) == "ImmersiveColorSet")
|
|
||||||
{
|
{
|
||||||
updateFunc?.Invoke();
|
updateFunc?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return IntPtr.Zero;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -929,6 +929,8 @@
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="300" />
|
<ColumnDefinition Width="300" />
|
||||||
|
|
@ -1003,9 +1005,40 @@
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbEchConfigList}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtEchConfigList"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="400"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{StaticResource DefTextBox}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="{x:Static resx:ResUI.TbEchForceQuery}" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="cmbEchForceQuery"
|
||||||
|
Grid.Row="6"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="200"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Style="{StaticResource DefComboBox}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="7"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="{x:Static resx:ResUI.TbCertPinning}" />
|
Text="{x:Static resx:ResUI.TbCertPinning}" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="5"
|
Grid.Row="7"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ public partial class AddServerWindow
|
||||||
cmbFingerprint2.ItemsSource = Global.Fingerprints;
|
cmbFingerprint2.ItemsSource = Global.Fingerprints;
|
||||||
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
|
cmbAllowInsecure.ItemsSource = Global.AllowInsecure;
|
||||||
cmbAlpn.ItemsSource = Global.Alpns;
|
cmbAlpn.ItemsSource = Global.Alpns;
|
||||||
|
cmbEchForceQuery.ItemsSource = Global.EchForceQuerys;
|
||||||
|
|
||||||
var lstStreamSecurity = new List<string>();
|
var lstStreamSecurity = new List<string>();
|
||||||
lstStreamSecurity.Add(string.Empty);
|
lstStreamSecurity.Add(string.Empty);
|
||||||
|
|
@ -182,6 +183,10 @@ public partial class AddServerWindow
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Alpn, v => v.cmbAlpn.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.CertTip, v => v.labCertPinning.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.Cert, v => v.txtCert.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.EchForceQuery, v => v.cmbEchForceQuery.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
//reality
|
//reality
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Sni, v => v.txtSNI2.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Fingerprint, v => v.cmbFingerprint2.Text).DisposeWith(disposables);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
Title="v2rayN"
|
Title="v2rayN"
|
||||||
Width="1200"
|
Width="1200"
|
||||||
Height="800"
|
Height="800"
|
||||||
MinWidth="900"
|
MinWidth="800"
|
||||||
x:TypeArguments="vms:MainWindowViewModel"
|
x:TypeArguments="vms:MainWindowViewModel"
|
||||||
Icon="/Resources/v2rayN.ico"
|
Icon="/Resources/v2rayN.ico"
|
||||||
ResizeMode="CanResizeWithGrip"
|
ResizeMode="CanResizeWithGrip"
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,7 @@ public partial class MainWindow
|
||||||
|
|
||||||
public void ShowHideWindow(bool? blShow)
|
public void ShowHideWindow(bool? blShow)
|
||||||
{
|
{
|
||||||
var bl = blShow ?? !_config.UiItem.ShowInTaskbar;
|
var bl = blShow ?? !AppManager.Instance.ShowInTaskbar;
|
||||||
if (bl)
|
if (bl)
|
||||||
{
|
{
|
||||||
this?.Show();
|
this?.Show();
|
||||||
|
|
@ -391,7 +391,7 @@ public partial class MainWindow
|
||||||
{
|
{
|
||||||
this?.Hide();
|
this?.Hide();
|
||||||
}
|
}
|
||||||
_config.UiItem.ShowInTaskbar = bl;
|
AppManager.Instance.ShowInTaskbar = bl;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnLoaded(object? sender, RoutedEventArgs e)
|
protected override void OnLoaded(object? sender, RoutedEventArgs e)
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,26 @@
|
||||||
HeadersVisibility="Column"
|
HeadersVisibility="Column"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
Style="{StaticResource DefDataGrid}">
|
Style="{StaticResource DefDataGrid}">
|
||||||
|
<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu Style="{StaticResource DefContextMenu}">
|
||||||
|
<MenuItem
|
||||||
|
x:Name="menuSubAdd2"
|
||||||
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
Header="{x:Static resx:ResUI.menuSubAdd}" />
|
||||||
|
<MenuItem
|
||||||
|
x:Name="menuSubDelete2"
|
||||||
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
Header="{x:Static resx:ResUI.menuSubDelete}" />
|
||||||
|
<MenuItem
|
||||||
|
x:Name="menuSubEdit2"
|
||||||
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
Header="{x:Static resx:ResUI.menuSubEdit}" />
|
||||||
|
<MenuItem
|
||||||
|
x:Name="menuSubShare2"
|
||||||
|
Height="{StaticResource MenuItemHeight}"
|
||||||
|
Header="{x:Static resx:ResUI.menuSubShare}" />
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn
|
<DataGridTextColumn
|
||||||
Width="*"
|
Width="*"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ public partial class SubSettingWindow
|
||||||
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubAddCmd, v => v.menuSubAdd2).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubDeleteCmd, v => v.menuSubDelete2).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubEditCmd, v => v.menuSubEdit2).DisposeWith(disposables);
|
||||||
|
this.BindCommand(ViewModel, vm => vm.SubShareCmd, v => v.menuSubShare2).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
|
WindowsUtils.SetDarkBorder(this, AppManager.Instance.Config.UiItem.CurrentTheme);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue