Compare commits

...

15 commits

Author SHA1 Message Date
2dust
fe183798b6 Refactor child item aggregation in managers
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2026-01-13 20:24:52 +08:00
2dust
947c84cf10 Refactor 'Move to Group' menu in ProfilesView
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2026-01-10 15:14:58 +08:00
2dust
9c74b51d74 up 7.17.0
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2026-01-09 18:44:28 +08:00
2dust
abd962ab31 Update Global.cs
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2026-01-08 17:15:59 +08:00
DHR60
f3b894015e
Add sing-box ech support (#8603)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
* Add sing-box ech support

* Support group config type

* Simplified code
2026-01-08 13:56:45 +08:00
2dust
4562d4cf00 Add ECH config support to profile and UI
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
Introduces EchConfigList and EchForceQuery fields to ProfileItem and V2rayConfig models, updates related handlers and services to process these fields, and extends the AddServerWindow UI to allow user input for ECH configuration. Also adds localization entries for the new fields and updates extension methods for string handling.
2026-01-07 11:34:13 +08:00
JieXu
bc36cf8a47
Code Clean (#8586)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2026-01-05 09:56:43 +08:00
Kazuto Iris
cbdfe2e15a
fix: Fix failure to follow system theme changes (#8584)
Fix the issue where the application failed to sync with system dark/light mode changes in specific scenarios such as triggering system theme switching via scheduled tasks while waking from hibernation, caused by the unreliable HWND hook implementation that missed critical events.
2026-01-05 09:56:33 +08:00
2dust
68583e20bc Update package versions in Directory.Packages.props
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2026-01-03 19:06:17 +08:00
DHR60
6d6459b009
Fix edge cases (#8564)
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2026-01-03 10:20:27 +08:00
2dust
807562b69e Set all .NET publish tasks to self-contained
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2025-12-28 14:10:00 +08:00
2dust
654d7d83d0 up 7.16.9
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2025-12-25 18:34:10 +08:00
2dust
027252e687 Move ShowInTaskbar and RunningCoreType to AppManager
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-12-24 16:01:28 +08:00
2dust
5478c90180 Bug fix
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release Linux / rpm (push) Blocked by required conditions
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
https://github.com/2dust/v2rayN/issues/8515
2025-12-24 14:19:36 +08:00
DHR60
28f30d7e97
Revert "Add TLS ALPN check for WS (#8469)" (#8517)
This reverts commit 6e27dca6cd.
2025-12-24 13:38:08 +08:00
51 changed files with 711 additions and 486 deletions

View file

@ -37,11 +37,11 @@ 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

View file

@ -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

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.16.8</Version> <Version>7.17.0</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -14,14 +14,14 @@
<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.1" /> <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.7" /> <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" />

View file

@ -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)

View file

@ -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
} }

View file

@ -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,

View file

@ -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"))
{ {

View file

@ -193,19 +193,6 @@ public class ActionPrecheckManager
} }
} }
// ws with tls, tls alpn should contain "http/1.1" in xray core
// rfc6455
// https://github.com/XTLS/Xray-core/blob/81f8f398c7b2b845853b1e85087c6122acc6db0b/transport/internet/tls/tls.go#L95-L116
if (item.Network == nameof(ETransport.ws)
&& item.StreamSecurity == Global.StreamSecurity)
{
var alpnList = Utils.String2List(item.Alpn) ?? [];
if (alpnList.Count > 0 && !alpnList.Contains("http/1.1"))
{
errors.Add(ResUI.AlpnMustContainHttp11ForWsTls);
}
}
return errors; return errors;
} }
@ -227,9 +214,10 @@ public class ActionPrecheckManager
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)
{ {

View file

@ -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

View file

@ -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;
}
} }

View file

@ -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;

View file

@ -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
} }

View file

@ -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

View file

@ -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; }

View file

@ -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; }
} }

View file

@ -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

View file

@ -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

View file

@ -78,15 +78,6 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 ALPN must contain &apos;http/1.1&apos; when using WebSocket with TLS. 的本地化字符串。
/// </summary>
public static string AlpnMustContainHttp11ForWsTls {
get {
return ResourceManager.GetString("AlpnMustContainHttp11ForWsTls", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Export share link to clipboard successfully 的本地化字符串。 /// 查找类似 Export share link to clipboard successfully 的本地化字符串。
/// </summary> /// </summary>
@ -2799,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>

View file

@ -1641,7 +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="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1638,7 +1638,10 @@ Si un certificat auto-signé est utilisé ou si le système contient une CA non
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>Élément de config 2 : choisir et ajouter depuis self-hosted</value> <value>Élément de config 2 : choisir et ajouter depuis self-hosted</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>Avec WebSocket et TLS, lALPN doit inclure http/1.1.</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1641,7 +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="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1641,7 +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="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1641,7 +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="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1638,7 +1638,10 @@
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>子配置项二,从自建中选择添加</value> <value>子配置项二,从自建中选择添加</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>使用 WebSocket+TLS 时ALPN 必须包含 'http/1.1'。</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -1638,7 +1638,10 @@
<data name="menuServerList2" xml:space="preserve"> <data name="menuServerList2" xml:space="preserve">
<value>子配置項二,從自建中選擇新增</value> <value>子配置項二,從自建中選擇新增</value>
</data> </data>
<data name="AlpnMustContainHttp11ForWsTls" xml:space="preserve"> <data name="TbEchConfigList" xml:space="preserve">
<value>ALPN must contain 'http/1.1' when using WebSocket with TLS.</value> <value>EchConfigList</value>
</data>
<data name="TbEchForceQuery" xml:space="preserve">
<value>EchForceQuery</value>
</data> </data>
</root> </root>

View file

@ -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;

View file

@ -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()

View file

@ -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()
{ {

View file

@ -334,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)
@ -354,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
@ -362,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;
@ -396,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()
@ -408,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;
@ -904,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));
}
} }

View file

@ -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())
{ {

View file

@ -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);
} }

View file

@ -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()

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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();

View file

@ -44,7 +44,7 @@ public class MsgViewModel : MyReactiveObject
EnqueueQueueMsg(msg); EnqueueQueueMsg(msg);
if (!_config.UiItem.ShowInTaskbar) if (!AppManager.Instance.ShowInTaskbar)
{ {
return; return;
} }

View file

@ -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 () =>

View file

@ -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;

View file

@ -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">

View file

@ -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);

View file

@ -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)

View file

@ -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

View file

@ -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);

View file

@ -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;
});
} }
} }

View file

@ -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">

View file

@ -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);

View file

@ -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)