Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3693a7fee6 | ||
|
|
a452bbe140 | ||
|
|
185c5e4bfb | ||
|
|
bbe64aa970 | ||
|
|
513662d89a | ||
|
|
22f0d04f01 | ||
|
|
d7c5161431 | ||
|
|
12cc09d0c9 | ||
|
|
5b12c36da5 | ||
|
|
e970372a9f | ||
|
|
5d6c5da9d9 | ||
|
|
ade2db3903 | ||
|
|
7f07279a4c | ||
|
|
b25d4d57bd | ||
|
|
46edd8f9a4 | ||
|
|
ebb95b5ee8 | ||
|
|
dc4611a258 | ||
|
|
03d5b7a05b | ||
|
|
a652fd879b | ||
|
|
326bf334e7 | ||
|
|
21a773f400 | ||
|
|
d86003df55 | ||
|
|
faff8e4ea2 | ||
|
|
6b85aa0b03 | ||
|
|
671678724b | ||
|
|
e96a4818c4 | ||
|
|
0377e7ce19 | ||
|
|
6929886b3e | ||
|
|
721d70c8c7 | ||
|
|
27b45aee83 | ||
|
|
18ac76e683 | ||
|
|
3e1e23a524 | ||
|
|
534c7ab444 | ||
|
|
c2c13ad318 | ||
|
|
3a21596d95 | ||
|
|
ef30d389dc | ||
|
|
bf8783fed7 | ||
|
|
4e042295d2 | ||
|
|
33d9c5db6c | ||
|
|
cb182125f6 | ||
|
|
ec627bdb82 | ||
|
|
4606e78570 | ||
|
|
f00e968b8f | ||
|
|
a87a015c03 | ||
|
|
c559914ff7 | ||
|
|
436d95576e | ||
|
|
54e83391d0 | ||
|
|
3e0578f775 | ||
|
|
29a5abf4d6 | ||
|
|
b54c67d6f1 | ||
|
|
b49486cc23 | ||
|
|
b95830b3d5 | ||
|
|
8e0c5cb9aa | ||
|
|
6ffb3bd30c | ||
|
|
2826444ffc | ||
|
|
56c3e9c46d | ||
|
|
0770e30034 | ||
|
|
04195c2957 | ||
|
|
d18d74ac1c |
2
.github/workflows/build-linux.yml
vendored
2
.github/workflows/build-linux.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
configuration: [Release]
|
configuration: [Release]
|
||||||
|
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -1,14 +1,67 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Install deps
|
||||||
sudo apt update -y
|
sudo apt update -y
|
||||||
sudo apt install -y libfuse2
|
sudo apt install -y libfuse2 wget file
|
||||||
wget -O pkg2appimage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage
|
|
||||||
chmod a+x pkg2appimage
|
# Get tools
|
||||||
export AppImageOutputArch=$OutputArch
|
wget -qO appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
export OutputPath=$OutputPath64
|
chmod +x appimagetool
|
||||||
./pkg2appimage ./pkg2appimage.yml
|
|
||||||
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
|
# x86_64 AppDir
|
||||||
export AppImageOutputArch=$OutputArchArm
|
APPDIR_X64="AppDir-x86_64"
|
||||||
export OutputPath=$OutputPathArm64
|
rm -rf "$APPDIR_X64"
|
||||||
./pkg2appimage ./pkg2appimage.yml
|
mkdir -p "$APPDIR_X64/usr/lib/v2rayN" "$APPDIR_X64/usr/bin" "$APPDIR_X64/usr/share/applications" "$APPDIR_X64/usr/share/pixmaps"
|
||||||
mv out/*.AppImage v2rayN-${AppImageOutputArch}.AppImage
|
cp -rf "$OutputPath64"/* "$APPDIR_X64/usr/lib/v2rayN" || true
|
||||||
|
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/usr/share/pixmaps/v2rayN.png" || true
|
||||||
|
[ -f "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_X64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_X64/v2rayN.png" || true
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_X64/AppRun"
|
||||||
|
chmod +x "$APPDIR_X64/AppRun"
|
||||||
|
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_X64/usr/bin/v2rayN"
|
||||||
|
cat > "$APPDIR_X64/v2rayN.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=v2rayN
|
||||||
|
Icon=v2rayN
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
install -Dm644 "$APPDIR_X64/v2rayN.desktop" "$APPDIR_X64/usr/share/applications/v2rayN.desktop"
|
||||||
|
|
||||||
|
ARCH=x86_64 ./appimagetool "$APPDIR_X64" "v2rayN-${OutputArch}.AppImage"
|
||||||
|
file "v2rayN-${OutputArch}.AppImage" | grep -q 'x86-64'
|
||||||
|
|
||||||
|
# aarch64 AppDir
|
||||||
|
APPDIR_ARM64="AppDir-aarch64"
|
||||||
|
rm -rf "$APPDIR_ARM64"
|
||||||
|
mkdir -p "$APPDIR_ARM64/usr/lib/v2rayN" "$APPDIR_ARM64/usr/bin" "$APPDIR_ARM64/usr/share/applications" "$APPDIR_ARM64/usr/share/pixmaps"
|
||||||
|
cp -rf "$OutputPathArm64"/* "$APPDIR_ARM64/usr/lib/v2rayN" || true
|
||||||
|
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/usr/share/pixmaps/v2rayN.png" || true
|
||||||
|
[ -f "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" ] && cp "$APPDIR_ARM64/usr/lib/v2rayN/v2rayN.png" "$APPDIR_ARM64/v2rayN.png" || true
|
||||||
|
|
||||||
|
printf '%s\n' '#!/bin/sh' 'HERE="$(dirname "$(readlink -f "$0")")"' 'cd "$HERE/usr/lib/v2rayN"' 'exec "$HERE/usr/lib/v2rayN/v2rayN" "$@"' > "$APPDIR_ARM64/AppRun"
|
||||||
|
chmod +x "$APPDIR_ARM64/AppRun"
|
||||||
|
ln -sf usr/lib/v2rayN/v2rayN "$APPDIR_ARM64/usr/bin/v2rayN"
|
||||||
|
cat > "$APPDIR_ARM64/v2rayN.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
|
Exec=v2rayN
|
||||||
|
Icon=v2rayN
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
install -Dm644 "$APPDIR_ARM64/v2rayN.desktop" "$APPDIR_ARM64/usr/share/applications/v2rayN.desktop"
|
||||||
|
|
||||||
|
# aarch64 runtime
|
||||||
|
wget -qO runtime-aarch64 https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-aarch64
|
||||||
|
chmod +x runtime-aarch64
|
||||||
|
|
||||||
|
# build aarch64 AppImage
|
||||||
|
ARCH=aarch64 ./appimagetool --runtime-file ./runtime-aarch64 "$APPDIR_ARM64" "v2rayN-${OutputArchArm}.AppImage"
|
||||||
|
file "v2rayN-${OutputArchArm}.AppImage" | grep -q 'ARM aarch64'
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Package: v2rayN
|
|||||||
Version: $Version
|
Version: $Version
|
||||||
Architecture: $Arch2
|
Architecture: $Arch2
|
||||||
Maintainer: https://github.com/2dust/v2rayN
|
Maintainer: https://github.com/2dust/v2rayN
|
||||||
|
Depends: desktop-file-utils, xdg-utils
|
||||||
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ===== Require Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ====
|
# == Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS OR Ubuntu/Debian ==
|
||||||
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|centos|ubuntu|debian)
|
rhel|rocky|almalinux|fedora|centos|ubuntu|debian)
|
||||||
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -390,25 +390,30 @@ download_mihomo() {
|
|||||||
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
chmod +x "$outroot/bin/mihomo/mihomo" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Move geo files to a unified path: outroot/bin/xray/
|
# Move geo files to a unified path: outroot/bin
|
||||||
unify_geo_layout() {
|
unify_geo_layout() {
|
||||||
local outroot="$1"
|
local outroot="$1"
|
||||||
mkdir -p "$outroot/bin/xray"
|
mkdir -p "$outroot/bin"
|
||||||
local srcs=( \
|
local names=( \
|
||||||
"$outroot/bin/geosite.dat" \
|
"geosite.dat" \
|
||||||
"$outroot/bin/geoip.dat" \
|
"geoip.dat" \
|
||||||
"$outroot/bin/geoip-only-cn-private.dat" \
|
"geoip-only-cn-private.dat" \
|
||||||
"$outroot/bin/Country.mmdb" \
|
"Country.mmdb" \
|
||||||
"$outroot/bin/geoip.metadb" \
|
"geoip.metadb" \
|
||||||
)
|
)
|
||||||
for s in "${srcs[@]}"; do
|
for n in "${names[@]}"; do
|
||||||
if [[ -f "$s" ]]; then
|
# If file exists under bin/xray/, move it up to bin/
|
||||||
mv -f "$s" "$outroot/bin/xray/$(basename "$s")"
|
if [[ -f "$outroot/bin/xray/$n" ]]; then
|
||||||
|
mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n"
|
||||||
|
fi
|
||||||
|
# If file already in bin/, leave it as-is
|
||||||
|
if [[ -f "$outroot/bin/$n" ]]; then
|
||||||
|
:
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download geo/rule assets; then unify to bin/xray/
|
# Download geo/rule assets; then unify to bin/
|
||||||
download_geo_assets() {
|
download_geo_assets() {
|
||||||
local outroot="$1"
|
local outroot="$1"
|
||||||
local bin_dir="$outroot/bin"
|
local bin_dir="$outroot/bin"
|
||||||
@@ -442,7 +447,7 @@ download_geo_assets() {
|
|||||||
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Unify to bin/xray/
|
# Unify to bin/
|
||||||
unify_geo_layout "$outroot"
|
unify_geo_layout "$outroot"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +485,7 @@ download_v2rayn_bundle() {
|
|||||||
rm -rf "$nested_dir"
|
rm -rf "$nested_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Unify to bin/xray/
|
# Unify to bin/
|
||||||
unify_geo_layout "$outroot"
|
unify_geo_layout "$outroot"
|
||||||
|
|
||||||
echo "[+] Bundle extracted to $outroot"
|
echo "[+] Bundle extracted to $outroot"
|
||||||
@@ -610,7 +615,7 @@ Source0: __PKGROOT__.tar.gz
|
|||||||
|
|
||||||
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
||||||
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
||||||
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL
|
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL, xdg-utils
|
||||||
|
|
||||||
%description
|
%description
|
||||||
v2rayN Linux for Red Hat Enterprise Linux
|
v2rayN Linux for Red Hat Enterprise Linux
|
||||||
@@ -629,25 +634,13 @@ https://github.com/2dust/v2rayN
|
|||||||
install -dm0755 %{buildroot}/opt/v2rayN
|
install -dm0755 %{buildroot}/opt/v2rayN
|
||||||
cp -a * %{buildroot}/opt/v2rayN/
|
cp -a * %{buildroot}/opt/v2rayN/
|
||||||
|
|
||||||
# Launcher (prefer native ELF first, then DLL fallback; also create Geo symlinks for the user)
|
# Launcher (prefer native ELF first, then DLL fallback)
|
||||||
install -dm0755 %{buildroot}%{_bindir}
|
install -dm0755 %{buildroot}%{_bindir}
|
||||||
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
DIR="/opt/v2rayN"
|
DIR="/opt/v2rayN"
|
||||||
|
|
||||||
# --- Symlink GEO files into user's XDG dir (first-run convenience) ---
|
|
||||||
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
||||||
USR_GEO_DIR="$XDG_DATA_HOME/v2rayN/bin"
|
|
||||||
SYS_XRAY_DIR="$DIR/bin/xray"
|
|
||||||
mkdir -p "$USR_GEO_DIR"
|
|
||||||
for f in geosite.dat geoip.dat geoip-only-cn-private.dat Country.mmdb; do
|
|
||||||
if [[ -f "$SYS_XRAY_DIR/$f" && ! -e "$USR_GEO_DIR/$f" ]]; then
|
|
||||||
ln -s "$SYS_XRAY_DIR/$f" "$USR_GEO_DIR/$f" || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
# --- end GEO ---
|
|
||||||
|
|
||||||
# Prefer native apphost
|
# Prefer native apphost
|
||||||
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
app: v2rayN
|
|
||||||
binpatch: true
|
|
||||||
|
|
||||||
ingredients:
|
|
||||||
script:
|
|
||||||
- export FileName="v2rayN-${AppImageOutputArch}.zip"
|
|
||||||
- wget -nv -O $FileName "https://github.com/2dust/v2rayN-core-bin/raw/refs/heads/master/${FileName}"
|
|
||||||
- 7z x $FileName -aoa
|
|
||||||
- cp -rf v2rayN-${AppImageOutputArch}/* $OutputPath
|
|
||||||
|
|
||||||
script:
|
|
||||||
- mkdir -p usr/bin usr/lib
|
|
||||||
- cp -rf $OutputPath usr/lib/v2rayN
|
|
||||||
- echo "When this file exists, app will not store configs under this folder" > usr/lib/v2rayN/NotStoreConfigHere.txt
|
|
||||||
- ln -sf usr/lib/v2rayN/v2rayN usr/bin/v2rayN
|
|
||||||
- chmod a+x usr/lib/v2rayN/v2rayN
|
|
||||||
- find usr -type f -exec sh -c 'file "{}" | grep -qi "executable" && chmod +x "{}"' \;
|
|
||||||
- install -Dm644 usr/lib/v2rayN/v2rayN.png v2rayN.png
|
|
||||||
- install -Dm644 usr/lib/v2rayN/v2rayN.png usr/share/pixmaps/v2rayN.png
|
|
||||||
- cat > v2rayN.desktop <<EOF
|
|
||||||
- [Desktop Entry]
|
|
||||||
- Name=v2rayN
|
|
||||||
- Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others
|
|
||||||
- Exec=v2rayN
|
|
||||||
- Icon=v2rayN
|
|
||||||
- Terminal=false
|
|
||||||
- Type=Application
|
|
||||||
- Categories=Network;
|
|
||||||
- EOF
|
|
||||||
- install -Dm644 v2rayN.desktop usr/share/applications/v2rayN.desktop
|
|
||||||
- cat > AppRun <<\EOF
|
|
||||||
- #!/bin/sh
|
|
||||||
- HERE="$(dirname "$(readlink -f "${0}")")"
|
|
||||||
- cd ${HERE}/usr/lib/v2rayN
|
|
||||||
- exec ${HERE}/usr/lib/v2rayN/v2rayN $@
|
|
||||||
- EOF
|
|
||||||
- chmod a+x AppRun
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>7.14.7</Version>
|
<Version>7.15.1</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
<CentralPackageVersionOverrideEnabled>false</CentralPackageVersionOverrideEnabled>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.5" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.5" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.7" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.5" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.7" />
|
||||||
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.5" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.7" />
|
||||||
|
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.7" />
|
||||||
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
<PackageVersion Include="CliWrap" Version="3.9.0" />
|
||||||
<PackageVersion Include="Downloader" Version="4.0.3" />
|
<PackageVersion Include="Downloader" Version="4.0.3" />
|
||||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||||
@@ -18,9 +19,10 @@
|
|||||||
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
|
<PackageVersion Include="ReactiveUI" Version="20.4.1" />
|
||||||
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
<PackageVersion Include="ReactiveUI.Fody" Version="19.5.41" />
|
||||||
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
<PackageVersion Include="ReactiveUI.WPF" Version="20.4.1" />
|
||||||
<PackageVersion Include="Semi.Avalonia" Version="11.2.1.9" />
|
<PackageVersion Include="Semi.Avalonia" Version="11.3.7" />
|
||||||
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.2.1.9" />
|
<PackageVersion Include="Semi.Avalonia.AvaloniaEdit" Version="11.2.0.1" />
|
||||||
<PackageVersion Include="Splat.NLog" Version="16.2.1" />
|
<PackageVersion Include="Semi.Avalonia.DataGrid" Version="11.3.7" />
|
||||||
|
<PackageVersion Include="NLog" Version="6.0.4" />
|
||||||
<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" />
|
||||||
|
|||||||
@@ -9,6 +9,31 @@ public class JsonUtils
|
|||||||
{
|
{
|
||||||
private static readonly string _tag = "JsonUtils";
|
private static readonly string _tag = "JsonUtils";
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
ReadCommentHandling = JsonCommentHandling.Skip
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
|
||||||
|
{
|
||||||
|
CommentHandling = JsonCommentHandling.Skip
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DeepCopy
|
/// DeepCopy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,11 +59,7 @@ public class JsonUtils
|
|||||||
{
|
{
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
var options = new JsonSerializerOptions
|
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
};
|
|
||||||
return JsonSerializer.Deserialize<T>(strJson, options);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -59,7 +80,7 @@ public class JsonUtils
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return JsonNode.Parse(strJson);
|
return JsonNode.Parse(strJson, nodeOptions: null, _defaultDocumentOptions);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -84,12 +105,7 @@ public class JsonUtils
|
|||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
var options = new JsonSerializerOptions
|
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
|
||||||
{
|
|
||||||
WriteIndented = indented,
|
|
||||||
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull,
|
|
||||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
|
||||||
};
|
|
||||||
result = JsonSerializer.Serialize(obj, options);
|
result = JsonSerializer.Serialize(obj, options);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -67,116 +67,4 @@ public static class ProcUtils
|
|||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task ProcessKill(int pid)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ProcessKill(Process.GetProcessById(pid), false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task ProcessKill(Process? proc, bool review)
|
|
||||||
{
|
|
||||||
if (proc is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetProcessKeyInfo(proc, review, out var procId, out var fileName, out var processName);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Utils.IsNonWindows())
|
|
||||||
{
|
|
||||||
proc?.Kill(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
proc?.Kill();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
proc?.Close();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
proc?.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(300);
|
|
||||||
await ProcessKillByKeyInfo(review, procId, fileName, processName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GetProcessKeyInfo(Process? proc, bool review, out int? procId, out string? fileName, out string? processName)
|
|
||||||
{
|
|
||||||
procId = null;
|
|
||||||
fileName = null;
|
|
||||||
processName = null;
|
|
||||||
if (!review)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
procId = proc?.Id;
|
|
||||||
fileName = proc?.MainModule?.FileName;
|
|
||||||
processName = proc?.ProcessName;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task ProcessKillByKeyInfo(bool review, int? procId, string? fileName, string? processName)
|
|
||||||
{
|
|
||||||
if (review && procId != null && fileName != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var lstProc = Process.GetProcessesByName(processName);
|
|
||||||
foreach (var proc2 in lstProc)
|
|
||||||
{
|
|
||||||
if (proc2.Id == procId)
|
|
||||||
{
|
|
||||||
Logging.SaveLog($"{_tag}, KillProcess not completing the job, procId");
|
|
||||||
await ProcessKill(proc2, false);
|
|
||||||
}
|
|
||||||
if (proc2.MainModule != null && proc2.MainModule?.FileName == fileName)
|
|
||||||
{
|
|
||||||
Logging.SaveLog($"{_tag}, KillProcess not completing the job, fileName");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logging.SaveLog(_tag, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using QRCoder;
|
using QRCoder;
|
||||||
|
using QRCoder.Exceptions;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using ZXing.SkiaSharp;
|
using ZXing.SkiaSharp;
|
||||||
|
|
||||||
@@ -8,10 +9,45 @@ public class QRCodeUtils
|
|||||||
{
|
{
|
||||||
public static byte[]? GenQRCode(string? url)
|
public static byte[]? GenQRCode(string? url)
|
||||||
{
|
{
|
||||||
|
if (url.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
using QRCodeGenerator qrGenerator = new();
|
using QRCodeGenerator qrGenerator = new();
|
||||||
using var qrCodeData = qrGenerator.CreateQrCode(url ?? string.Empty, QRCodeGenerator.ECCLevel.Q);
|
DataTooLongException? lastDtle = null;
|
||||||
using PngByteQRCode qrCode = new(qrCodeData);
|
|
||||||
return qrCode.GetGraphic(20);
|
var levels = new[]
|
||||||
|
{
|
||||||
|
QRCodeGenerator.ECCLevel.H,
|
||||||
|
QRCodeGenerator.ECCLevel.Q,
|
||||||
|
QRCodeGenerator.ECCLevel.M,
|
||||||
|
QRCodeGenerator.ECCLevel.L
|
||||||
|
};
|
||||||
|
foreach (var level in levels)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var qrCodeData = qrGenerator.CreateQrCode(url, level);
|
||||||
|
using PngByteQRCode qrCode = new(qrCodeData);
|
||||||
|
return qrCode.GetGraphic(20);
|
||||||
|
}
|
||||||
|
catch (DataTooLongException ex)
|
||||||
|
{
|
||||||
|
lastDtle = ex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDtle != null)
|
||||||
|
{
|
||||||
|
throw lastDtle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string? ParseBarcode(string? fileName)
|
public static string? ParseBarcode(string? fileName)
|
||||||
|
|||||||
@@ -85,13 +85,19 @@ public class Utils
|
|||||||
/// Base64 Encode
|
/// Base64 Encode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plainText"></param>
|
/// <param name="plainText"></param>
|
||||||
|
/// <param name="removePadding"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string Base64Encode(string plainText)
|
public static string Base64Encode(string plainText, bool removePadding = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
||||||
return Convert.ToBase64String(plainTextBytes);
|
var base64 = Convert.ToBase64String(plainTextBytes);
|
||||||
|
if (removePadding)
|
||||||
|
{
|
||||||
|
base64 = base64.TrimEnd('=');
|
||||||
|
}
|
||||||
|
return base64;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -112,7 +118,7 @@ public class Utils
|
|||||||
{
|
{
|
||||||
if (plainText.IsNullOrEmpty())
|
if (plainText.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
return "";
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
plainText = plainText.Trim()
|
plainText = plainText.Trim()
|
||||||
@@ -331,6 +337,32 @@ public class Utils
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
|
||||||
|
{
|
||||||
|
var userHostsMap = hostsContent
|
||||||
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(line => line.Trim())
|
||||||
|
// skip full-line comments
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
|
||||||
|
// strip inline comments (truncate at '#')
|
||||||
|
.Select(line =>
|
||||||
|
{
|
||||||
|
var index = line.IndexOf('#');
|
||||||
|
return index >= 0 ? line.Substring(0, index).Trim() : line;
|
||||||
|
})
|
||||||
|
// ensure line still contains valid parts
|
||||||
|
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
||||||
|
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Where(parts => parts.Length >= 2)
|
||||||
|
.GroupBy(parts => parts[0])
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
||||||
|
);
|
||||||
|
|
||||||
|
return userHostsMap;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion 转换函数
|
#endregion 转换函数
|
||||||
|
|
||||||
#region 数据检查
|
#region 数据检查
|
||||||
@@ -582,9 +614,9 @@ public class Utils
|
|||||||
if (host.StartsWith("#"))
|
if (host.StartsWith("#"))
|
||||||
continue;
|
continue;
|
||||||
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
var hostItem = host.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (hostItem.Length != 2)
|
if (hostItem.Length < 2)
|
||||||
continue;
|
continue;
|
||||||
systemHosts.Add(hostItem.Last(), hostItem.First());
|
systemHosts.Add(hostItem[1], hostItem[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -857,6 +889,55 @@ public class Utils
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsPackagedInstall()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsWindows() || IsOSX())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var exePath = GetExePath();
|
||||||
|
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
|
||||||
|
var p = baseDir.Replace('\\', '/');
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(p))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.Contains("/.mount_", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.StartsWith("/usr/lib/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.StartsWith("/usr/share/v2rayN", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<string?> GetLinuxUserId()
|
private static async Task<string?> GetLinuxUserId()
|
||||||
{
|
{
|
||||||
var arg = new List<string>() { "-c", "id -u" };
|
var arg = new List<string>() { "-c", "id -u" };
|
||||||
@@ -872,7 +953,7 @@ public class Utils
|
|||||||
if (SetUnixFileMode(fileName))
|
if (SetUnixFileMode(fileName))
|
||||||
{
|
{
|
||||||
Logging.SaveLog($"Successfully set the file execution permission, {fileName}");
|
Logging.SaveLog($"Successfully set the file execution permission, {fileName}");
|
||||||
return "";
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileName.Contains(' '))
|
if (fileName.Contains(' '))
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ namespace ServiceLib.Common;
|
|||||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public sealed class Job : IDisposable
|
public sealed class WindowsJob : IDisposable
|
||||||
{
|
{
|
||||||
private IntPtr handle = IntPtr.Zero;
|
private IntPtr handle = IntPtr.Zero;
|
||||||
|
|
||||||
public Job()
|
public WindowsJob()
|
||||||
{
|
{
|
||||||
handle = CreateJobObject(IntPtr.Zero, null);
|
handle = CreateJobObject(IntPtr.Zero, null);
|
||||||
var extendedInfoPtr = IntPtr.Zero;
|
var extendedInfoPtr = IntPtr.Zero;
|
||||||
@@ -94,7 +94,7 @@ namespace ServiceLib.Common;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~Job()
|
~WindowsJob()
|
||||||
{
|
{
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,6 @@ public enum EViewAction
|
|||||||
ProfilesFocus,
|
ProfilesFocus,
|
||||||
ShareSub,
|
ShareSub,
|
||||||
ShareServer,
|
ShareServer,
|
||||||
ShowHideWindow,
|
|
||||||
ScanScreenTask,
|
ScanScreenTask,
|
||||||
ScanImageTask,
|
ScanImageTask,
|
||||||
BrowseServer,
|
BrowseServer,
|
||||||
|
|||||||
32
v2rayN/ServiceLib/Events/AppEvents.cs
Normal file
32
v2rayN/ServiceLib/Events/AppEvents.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Reactive;
|
||||||
|
|
||||||
|
namespace ServiceLib.Events;
|
||||||
|
|
||||||
|
public static class AppEvents
|
||||||
|
{
|
||||||
|
public static readonly EventChannel<Unit> ReloadRequested = new();
|
||||||
|
public static readonly EventChannel<bool?> ShowHideWindowRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> AddServerViaScanRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> AddServerViaClipboardRequested = new();
|
||||||
|
public static readonly EventChannel<bool> SubscriptionsUpdateRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> ProfilesRefreshRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> SubscriptionsRefreshRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> ProxiesReloadRequested = new();
|
||||||
|
public static readonly EventChannel<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<string> SendSnackMsgRequested = new();
|
||||||
|
public static readonly EventChannel<string> SendMsgViewRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> AppExitRequested = new();
|
||||||
|
public static readonly EventChannel<bool> ShutdownRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> AdjustMainLvColWidthRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<string> SetDefaultServerRequested = new();
|
||||||
|
|
||||||
|
public static readonly EventChannel<Unit> RoutingsMenuRefreshRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> TestServerRequested = new();
|
||||||
|
public static readonly EventChannel<Unit> InboundDisplayRequested = new();
|
||||||
|
public static readonly EventChannel<ESysProxyType> SysProxyChangeRequested = new();
|
||||||
|
}
|
||||||
29
v2rayN/ServiceLib/Events/EventChannel.cs
Normal file
29
v2rayN/ServiceLib/Events/EventChannel.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
|
||||||
|
namespace ServiceLib.Events;
|
||||||
|
|
||||||
|
public sealed class EventChannel<T>
|
||||||
|
{
|
||||||
|
private readonly ISubject<T> _subject = Subject.Synchronize(new Subject<T>());
|
||||||
|
|
||||||
|
public IObservable<T> AsObservable()
|
||||||
|
{
|
||||||
|
return _subject.AsObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Publish(T value)
|
||||||
|
{
|
||||||
|
_subject.OnNext(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Publish()
|
||||||
|
{
|
||||||
|
if (typeof(T) != typeof(Unit))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Publish() without value is only valid for EventChannel<Unit>.");
|
||||||
|
}
|
||||||
|
_subject.OnNext((T)(object)Unit.Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ public class Global
|
|||||||
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh";
|
public const string ProxySetLinuxShellFileName = NamespaceSample + "proxy_set_linux_sh";
|
||||||
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
public const string KillAsSudoOSXShellFileName = NamespaceSample + "kill_as_sudo_osx_sh";
|
||||||
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
public const string KillAsSudoLinuxShellFileName = NamespaceSample + "kill_as_sudo_linux_sh";
|
||||||
|
public const string SingboxFakeIPFilterFileName = NamespaceSample + "singbox_fakeip_filter";
|
||||||
|
|
||||||
public const string DefaultSecurity = "auto";
|
public const string DefaultSecurity = "auto";
|
||||||
public const string DefaultNetwork = "tcp";
|
public const string DefaultNetwork = "tcp";
|
||||||
@@ -448,6 +449,14 @@ public class Global
|
|||||||
"none"
|
"none"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly Dictionary<string, string> LogLevelColors = new()
|
||||||
|
{
|
||||||
|
{ "debug", "#6C757D" },
|
||||||
|
{ "info", "#2ECC71" },
|
||||||
|
{ "warning", "#FFA500" },
|
||||||
|
{ "error", "#E74C3C" },
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly List<string> InboundTags =
|
public static readonly List<string> InboundTags =
|
||||||
[
|
[
|
||||||
"socks",
|
"socks",
|
||||||
@@ -597,6 +606,7 @@ public class Global
|
|||||||
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
|
{ "cloudflare-dns.com", new List<string> { "104.16.249.249", "104.16.248.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9" } },
|
||||||
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
|
{ "dns.cloudflare.com", new List<string> { "104.16.132.229", "104.16.133.229", "2606:4700::6810:84e5", "2606:4700::6810:85e5" } },
|
||||||
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
{ "dot.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
||||||
|
{ "doh.pub", new List<string> { "1.12.12.12", "120.53.53.53" } },
|
||||||
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
|
{ "dns.quad9.net", new List<string> { "9.9.9.9", "149.112.112.112", "2620:fe::fe", "2620:fe::9" } },
|
||||||
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
|
{ "dns.yandex.net", new List<string> { "77.88.8.8", "77.88.8.1", "2a02:6b8::feed:0ff", "2a02:6b8:0:1::feed:0ff" } },
|
||||||
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
|
{ "dns.sb", new List<string> { "185.222.222.222", "2a09::" } },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
global using ServiceLib.Base;
|
global using ServiceLib.Base;
|
||||||
global using ServiceLib.Common;
|
global using ServiceLib.Common;
|
||||||
global using ServiceLib.Enums;
|
global using ServiceLib.Enums;
|
||||||
|
global using ServiceLib.Events;
|
||||||
global using ServiceLib.Handler;
|
global using ServiceLib.Handler;
|
||||||
global using ServiceLib.Helper;
|
global using ServiceLib.Helper;
|
||||||
global using ServiceLib.Manager;
|
global using ServiceLib.Manager;
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
using System.Reactive;
|
|
||||||
using System.Reactive.Subjects;
|
|
||||||
|
|
||||||
namespace ServiceLib.Handler;
|
|
||||||
|
|
||||||
public static class AppEvents
|
|
||||||
{
|
|
||||||
public static readonly Subject<Unit> ProfilesRefreshRequested = new();
|
|
||||||
|
|
||||||
public static readonly Subject<string> SendSnackMsgRequested = new();
|
|
||||||
|
|
||||||
public static readonly Subject<string> SendMsgViewRequested = new();
|
|
||||||
|
|
||||||
public static readonly Subject<Unit> AppExitRequested = new();
|
|
||||||
|
|
||||||
public static readonly Subject<bool> ShutdownRequested = new();
|
|
||||||
|
|
||||||
public static readonly Subject<Unit> AdjustMainLvColWidthRequested = new();
|
|
||||||
|
|
||||||
public static readonly Subject<ServerSpeedItem> DispatcherStatisticsRequested = new();
|
|
||||||
}
|
|
||||||
@@ -113,6 +113,10 @@ public static class ConfigHandler
|
|||||||
config.ConstItem ??= new ConstItem();
|
config.ConstItem ??= new ConstItem();
|
||||||
|
|
||||||
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
|
||||||
|
if (config.SimpleDNSItem.GlobalFakeIp is null)
|
||||||
|
{
|
||||||
|
config.SimpleDNSItem.GlobalFakeIp = true;
|
||||||
|
}
|
||||||
|
|
||||||
config.SpeedTestItem ??= new();
|
config.SpeedTestItem ??= new();
|
||||||
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
if (config.SpeedTestItem.SpeedTestTimeout < 10)
|
||||||
@@ -1210,11 +1214,11 @@ public static class ConfigHandler
|
|||||||
CoreType = ECoreType.sing_box,
|
CoreType = ECoreType.sing_box,
|
||||||
ConfigType = EConfigType.SOCKS,
|
ConfigType = EConfigType.SOCKS,
|
||||||
Address = Global.Loopback,
|
Address = Global.Loopback,
|
||||||
Sni = node.Address, //Tun2SocksAddress
|
SpiderX = node.Address, // Tun2SocksAddress
|
||||||
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
|
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
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 = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
|
||||||
itemSocks = new ProfileItem()
|
itemSocks = new ProfileItem()
|
||||||
@@ -2221,6 +2225,7 @@ public static class ConfigHandler
|
|||||||
UseSystemHosts = false,
|
UseSystemHosts = false,
|
||||||
AddCommonHosts = true,
|
AddCommonHosts = true,
|
||||||
FakeIP = false,
|
FakeIP = false,
|
||||||
|
GlobalFakeIp = true,
|
||||||
BlockBindingQuery = true,
|
BlockBindingQuery = true,
|
||||||
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
|
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
|
||||||
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
|
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
|
||||||
|
|||||||
@@ -155,61 +155,60 @@ public class BaseFmt
|
|||||||
|
|
||||||
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
|
protected static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
|
||||||
{
|
{
|
||||||
item.Flow = query["flow"] ?? "";
|
item.Flow = GetQueryValue(query, "flow");
|
||||||
item.StreamSecurity = query["security"] ?? "";
|
item.StreamSecurity = GetQueryValue(query, "security");
|
||||||
item.Sni = query["sni"] ?? "";
|
item.Sni = GetQueryValue(query, "sni");
|
||||||
item.Alpn = Utils.UrlDecode(query["alpn"] ?? "");
|
item.Alpn = GetQueryDecoded(query, "alpn");
|
||||||
item.Fingerprint = Utils.UrlDecode(query["fp"] ?? "");
|
item.Fingerprint = GetQueryDecoded(query, "fp");
|
||||||
item.PublicKey = Utils.UrlDecode(query["pbk"] ?? "");
|
item.PublicKey = GetQueryDecoded(query, "pbk");
|
||||||
item.ShortId = Utils.UrlDecode(query["sid"] ?? "");
|
item.ShortId = GetQueryDecoded(query, "sid");
|
||||||
item.SpiderX = Utils.UrlDecode(query["spx"] ?? "");
|
item.SpiderX = GetQueryDecoded(query, "spx");
|
||||||
item.Mldsa65Verify = Utils.UrlDecode(query["pqv"] ?? "");
|
item.Mldsa65Verify = GetQueryDecoded(query, "pqv");
|
||||||
item.AllowInsecure = (query["allowInsecure"] ?? "") == "1" ? "true" : "";
|
item.AllowInsecure = new[] { "allowInsecure", "allow_insecure", "insecure" }.Any(k => (query[k] ?? "") == "1") ? "true" : "";
|
||||||
|
|
||||||
item.Network = query["type"] ?? nameof(ETransport.tcp);
|
item.Network = GetQueryValue(query, "type", nameof(ETransport.tcp));
|
||||||
switch (item.Network)
|
switch (item.Network)
|
||||||
{
|
{
|
||||||
case nameof(ETransport.tcp):
|
case nameof(ETransport.tcp):
|
||||||
item.HeaderType = query["headerType"] ?? Global.None;
|
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
|
||||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.kcp):
|
case nameof(ETransport.kcp):
|
||||||
item.HeaderType = query["headerType"] ?? Global.None;
|
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
|
||||||
item.Path = Utils.UrlDecode(query["seed"] ?? "");
|
item.Path = GetQueryDecoded(query, "seed");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.ws):
|
case nameof(ETransport.ws):
|
||||||
case nameof(ETransport.httpupgrade):
|
case nameof(ETransport.httpupgrade):
|
||||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
item.Path = Utils.UrlDecode(query["path"] ?? "/");
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.xhttp):
|
case nameof(ETransport.xhttp):
|
||||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
item.Path = Utils.UrlDecode(query["path"] ?? "/");
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
item.HeaderType = Utils.UrlDecode(query["mode"] ?? "");
|
item.HeaderType = GetQueryDecoded(query, "mode");
|
||||||
item.Extra = Utils.UrlDecode(query["extra"] ?? "");
|
item.Extra = GetQueryDecoded(query, "extra");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.http):
|
case nameof(ETransport.http):
|
||||||
case nameof(ETransport.h2):
|
case nameof(ETransport.h2):
|
||||||
item.Network = nameof(ETransport.h2);
|
item.Network = nameof(ETransport.h2);
|
||||||
item.RequestHost = Utils.UrlDecode(query["host"] ?? "");
|
item.RequestHost = GetQueryDecoded(query, "host");
|
||||||
item.Path = Utils.UrlDecode(query["path"] ?? "/");
|
item.Path = GetQueryDecoded(query, "path", "/");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.quic):
|
case nameof(ETransport.quic):
|
||||||
item.HeaderType = query["headerType"] ?? Global.None;
|
item.HeaderType = GetQueryValue(query, "headerType", Global.None);
|
||||||
item.RequestHost = query["quicSecurity"] ?? Global.None;
|
item.RequestHost = GetQueryValue(query, "quicSecurity", Global.None);
|
||||||
item.Path = Utils.UrlDecode(query["key"] ?? "");
|
item.Path = GetQueryDecoded(query, "key");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(ETransport.grpc):
|
case nameof(ETransport.grpc):
|
||||||
item.RequestHost = Utils.UrlDecode(query["authority"] ?? "");
|
item.RequestHost = GetQueryDecoded(query, "authority");
|
||||||
item.Path = Utils.UrlDecode(query["serviceName"] ?? "");
|
item.Path = GetQueryDecoded(query, "serviceName");
|
||||||
item.HeaderType = Utils.UrlDecode(query["mode"] ?? Global.GrpcGunMode);
|
item.HeaderType = GetQueryDecoded(query, "mode", Global.GrpcGunMode);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -239,4 +238,14 @@ public class BaseFmt
|
|||||||
var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}";
|
var url = $"{Utils.UrlEncode(userInfo)}@{GetIpv6(address)}:{port}";
|
||||||
return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}";
|
return $"{Global.ProtocolShares[eConfigType]}{url}{query}{remark}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static string GetQueryValue(NameValueCollection query, string key, string defaultValue = "")
|
||||||
|
{
|
||||||
|
return query[key] ?? defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string GetQueryDecoded(NameValueCollection query, string key, string defaultValue = "")
|
||||||
|
{
|
||||||
|
return Utils.UrlDecode(GetQueryValue(query, key, defaultValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ public class ClashFmt : BaseFmt
|
|||||||
{
|
{
|
||||||
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
|
||||||
{
|
{
|
||||||
if (Contains(strData, "port", "socks-port", "proxies"))
|
if (Contains(strData, "external-controller", "-port", "proxies"))
|
||||||
{
|
{
|
||||||
var fileName = WriteAllText(strData, "yaml");
|
var fileName = WriteAllText(strData, "yaml");
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class FmtHandler
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.SaveLog(_tag, ex);
|
Logging.SaveLog(_tag, ex);
|
||||||
return "";
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ public class Hysteria2Fmt : BaseFmt
|
|||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
ResolveStdTransport(query, ref item);
|
ResolveStdTransport(query, ref item);
|
||||||
item.Path = Utils.UrlDecode(query["obfs-password"] ?? "");
|
item.Path = GetQueryDecoded(query, "obfs-password");
|
||||||
item.AllowInsecure = (query["insecure"] ?? "") == "1" ? "true" : "false";
|
item.AllowInsecure = GetQueryValue(query, "insecure") == "1" ? "true" : "false";
|
||||||
|
|
||||||
item.Ports = Utils.UrlDecode(query["mport"] ?? "");
|
item.Ports = GetQueryDecoded(query, "mport");
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class ShadowsocksFmt : BaseFmt
|
|||||||
// item.port);
|
// item.port);
|
||||||
//url = Utile.Base64Encode(url);
|
//url = Utile.Base64Encode(url);
|
||||||
//new Sip002
|
//new Sip002
|
||||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
|
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
||||||
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
|
return ToUri(EConfigType.Shadowsocks, item.Address, item.Port, pw, null, remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class SocksFmt : BaseFmt
|
|||||||
remark = "#" + Utils.UrlEncode(item.Remarks);
|
remark = "#" + Utils.UrlEncode(item.Remarks);
|
||||||
}
|
}
|
||||||
//new
|
//new
|
||||||
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}");
|
var pw = Utils.Base64Encode($"{item.Security}:{item.Id}", true);
|
||||||
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
|
return ToUri(EConfigType.SOCKS, item.Address, item.Port, pw, null, remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class TuicFmt : BaseFmt
|
|||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
ResolveStdTransport(query, ref item);
|
ResolveStdTransport(query, ref item);
|
||||||
item.HeaderType = query["congestion_control"] ?? "";
|
item.HeaderType = GetQueryValue(query, "congestion_control");
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class VLESSFmt : BaseFmt
|
|||||||
item.Id = Utils.UrlDecode(url.UserInfo);
|
item.Id = Utils.UrlDecode(url.UserInfo);
|
||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
item.Security = query["encryption"] ?? Global.None;
|
item.Security = GetQueryValue(query, "encryption", Global.None);
|
||||||
item.StreamSecurity = query["security"] ?? "";
|
item.StreamSecurity = GetQueryValue(query, "security");
|
||||||
_ = ResolveStdTransport(query, ref item);
|
_ = ResolveStdTransport(query, ref item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ public class WireguardFmt : BaseFmt
|
|||||||
|
|
||||||
var query = Utils.ParseQueryString(url.Query);
|
var query = Utils.ParseQueryString(url.Query);
|
||||||
|
|
||||||
item.PublicKey = Utils.UrlDecode(query["publickey"] ?? "");
|
item.PublicKey = GetQueryDecoded(query, "publickey");
|
||||||
item.Path = Utils.UrlDecode(query["reserved"] ?? "");
|
item.Path = GetQueryDecoded(query, "reserved");
|
||||||
item.RequestHost = Utils.UrlDecode(query["address"] ?? "");
|
item.RequestHost = GetQueryDecoded(query, "address");
|
||||||
item.ShortId = Utils.UrlDecode(query["mtu"] ?? "");
|
item.ShortId = GetQueryDecoded(query, "mtu");
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Reactive;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
public sealed class AppManager
|
public sealed class AppManager
|
||||||
@@ -10,7 +8,6 @@ public sealed class AppManager
|
|||||||
private Config _config;
|
private Config _config;
|
||||||
private int? _statePort;
|
private int? _statePort;
|
||||||
private int? _statePort2;
|
private int? _statePort2;
|
||||||
private Job? _processJob;
|
|
||||||
public static AppManager Instance => _instance.Value;
|
public static AppManager Instance => _instance.Value;
|
||||||
public Config Config => _config;
|
public Config Config => _config;
|
||||||
|
|
||||||
@@ -96,7 +93,7 @@ public sealed class AppManager
|
|||||||
Logging.SaveLog("AppExitAsync Begin");
|
Logging.SaveLog("AppExitAsync Begin");
|
||||||
|
|
||||||
await SysProxyHandler.UpdateSysProxy(_config, true);
|
await SysProxyHandler.UpdateSysProxy(_config, true);
|
||||||
AppEvents.AppExitRequested.OnNext(Unit.Default);
|
AppEvents.AppExitRequested.Publish();
|
||||||
await Task.Delay(50); //Wait for AppExitRequested to be processed
|
await Task.Delay(50); //Wait for AppExitRequested to be processed
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
@@ -119,7 +116,13 @@ public sealed class AppManager
|
|||||||
|
|
||||||
public void Shutdown(bool byUser)
|
public void Shutdown(bool byUser)
|
||||||
{
|
{
|
||||||
AppEvents.ShutdownRequested.OnNext(byUser);
|
AppEvents.ShutdownRequested.Publish(byUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RebootAsAdmin()
|
||||||
|
{
|
||||||
|
ProcUtils.RebootAsAdmin();
|
||||||
|
await AppManager.Instance.AppExitAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion App
|
#endregion App
|
||||||
@@ -132,21 +135,6 @@ public sealed class AppManager
|
|||||||
return localPort + (int)protocol;
|
return localPort + (int)protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddProcess(nint processHandle)
|
|
||||||
{
|
|
||||||
if (Utils.IsWindows())
|
|
||||||
{
|
|
||||||
_processJob ??= new();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_processJob?.AddProcess(processHandle);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Config
|
#endregion Config
|
||||||
|
|
||||||
#region SqliteHelper
|
#region SqliteHelper
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
@@ -31,7 +30,7 @@ public class CoreAdminManager
|
|||||||
await _updateFunc?.Invoke(notify, msg);
|
await _updateFunc?.Invoke(notify, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
public async Task<ProcessService?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.AppendLine("#!/bin/bash");
|
sb.AppendLine("#!/bin/bash");
|
||||||
@@ -39,50 +38,25 @@ public class CoreAdminManager
|
|||||||
sb.AppendLine($"sudo -S {cmdLine}");
|
sb.AppendLine($"sudo -S {cmdLine}");
|
||||||
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
var shFilePath = await FileManager.CreateLinuxShellFile("run_as_sudo.sh", sb.ToString(), true);
|
||||||
|
|
||||||
Process proc = new()
|
var procService = new ProcessService(
|
||||||
{
|
fileName: shFilePath,
|
||||||
StartInfo = new()
|
arguments: "",
|
||||||
{
|
workingDirectory: Utils.GetBinConfigPath(),
|
||||||
FileName = shFilePath,
|
displayLog: true,
|
||||||
Arguments = "",
|
redirectInput: true,
|
||||||
WorkingDirectory = Utils.GetBinConfigPath(),
|
environmentVars: null,
|
||||||
UseShellExecute = false,
|
updateFunc: _updateFunc
|
||||||
RedirectStandardInput = true,
|
);
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
StandardOutputEncoding = Encoding.UTF8,
|
|
||||||
StandardErrorEncoding = Encoding.UTF8,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void dataHandler(object sender, DataReceivedEventArgs e)
|
await procService.StartAsync(AppManager.Instance.LinuxSudoPwd);
|
||||||
{
|
|
||||||
if (e.Data.IsNotEmpty())
|
|
||||||
{
|
|
||||||
_ = UpdateFunc(false, e.Data + Environment.NewLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.OutputDataReceived += dataHandler;
|
if (procService is null or { HasExited: true })
|
||||||
proc.ErrorDataReceived += dataHandler;
|
|
||||||
|
|
||||||
proc.Start();
|
|
||||||
proc.BeginOutputReadLine();
|
|
||||||
proc.BeginErrorReadLine();
|
|
||||||
|
|
||||||
await Task.Delay(10);
|
|
||||||
await proc.StandardInput.WriteLineAsync(AppManager.Instance.LinuxSudoPwd);
|
|
||||||
|
|
||||||
await Task.Delay(100);
|
|
||||||
if (proc is null or { HasExited: true })
|
|
||||||
{
|
{
|
||||||
throw new Exception(ResUI.FailedToRunCore);
|
throw new Exception(ResUI.FailedToRunCore);
|
||||||
}
|
}
|
||||||
|
_linuxSudoPid = procService.Id;
|
||||||
|
|
||||||
_linuxSudoPid = proc.Id;
|
return procService;
|
||||||
|
|
||||||
return proc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task KillProcessAsLinuxSudo()
|
public async Task KillProcessAsLinuxSudo()
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -11,8 +8,9 @@ public class CoreManager
|
|||||||
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
private static readonly Lazy<CoreManager> _instance = new(() => new());
|
||||||
public static CoreManager Instance => _instance.Value;
|
public static CoreManager Instance => _instance.Value;
|
||||||
private Config _config;
|
private Config _config;
|
||||||
private Process? _process;
|
private WindowsJob? _processJob;
|
||||||
private Process? _processPre;
|
private ProcessService? _processService;
|
||||||
|
private ProcessService? _processPreService;
|
||||||
private bool _linuxSudo = false;
|
private bool _linuxSudo = false;
|
||||||
private Func<bool, string, Task>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private const string _tag = "CoreHandler";
|
private const string _tag = "CoreHandler";
|
||||||
@@ -89,13 +87,13 @@ public class CoreManager
|
|||||||
|
|
||||||
await CoreStart(node);
|
await CoreStart(node);
|
||||||
await CoreStartPreService(node);
|
await CoreStartPreService(node);
|
||||||
if (_process != null)
|
if (_processService != null)
|
||||||
{
|
{
|
||||||
await UpdateFunc(true, $"{node.GetSummary()}");
|
await UpdateFunc(true, $"{node.GetSummary()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
|
public async Task<ProcessService?> LoadCoreConfigSpeedtest(List<ServerTestItem> selecteds)
|
||||||
{
|
{
|
||||||
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray;
|
var coreType = selecteds.Exists(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) ? ECoreType.sing_box : ECoreType.Xray;
|
||||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||||
@@ -104,28 +102,22 @@ public class CoreManager
|
|||||||
await UpdateFunc(false, result.Msg);
|
await UpdateFunc(false, result.Msg);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
return -1;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
await UpdateFunc(false, string.Format(ResUI.StartService, DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));
|
||||||
await UpdateFunc(false, configPath);
|
await UpdateFunc(false, configPath);
|
||||||
|
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
return await RunProcess(coreInfo, fileName, true, false);
|
||||||
if (proc is null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return proc.Id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> LoadCoreConfigSpeedtest(ServerTestItem testItem)
|
public async Task<ProcessService?> LoadCoreConfigSpeedtest(ServerTestItem testItem)
|
||||||
{
|
{
|
||||||
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
|
var node = await AppManager.Instance.GetProfileItem(testItem.IndexId);
|
||||||
if (node is null)
|
if (node is null)
|
||||||
{
|
{
|
||||||
return -1;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false));
|
||||||
@@ -133,18 +125,12 @@ public class CoreManager
|
|||||||
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
|
var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath);
|
||||||
if (result.Success != true)
|
if (result.Success != true)
|
||||||
{
|
{
|
||||||
return -1;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType);
|
||||||
var proc = await RunProcess(coreInfo, fileName, true, false);
|
return await RunProcess(coreInfo, fileName, true, false);
|
||||||
if (proc is null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return proc.Id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CoreStop()
|
public async Task CoreStop()
|
||||||
@@ -157,16 +143,18 @@ public class CoreManager
|
|||||||
_linuxSudo = false;
|
_linuxSudo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_process != null)
|
if (_processService != null)
|
||||||
{
|
{
|
||||||
await ProcUtils.ProcessKill(_process, Utils.IsWindows());
|
await _processService.StopAsync();
|
||||||
_process = null;
|
_processService.Dispose();
|
||||||
|
_processService = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_processPre != null)
|
if (_processPreService != null)
|
||||||
{
|
{
|
||||||
await ProcUtils.ProcessKill(_processPre, Utils.IsWindows());
|
await _processPreService.StopAsync();
|
||||||
_processPre = null;
|
_processPreService.Dispose();
|
||||||
|
_processPreService = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -188,12 +176,12 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_process = proc;
|
_processService = proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CoreStartPreService(ProfileItem node)
|
private async Task CoreStartPreService(ProfileItem node)
|
||||||
{
|
{
|
||||||
if (_process != null && !_process.HasExited)
|
if (_processService != null && !_processService.HasExited)
|
||||||
{
|
{
|
||||||
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
|
||||||
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType);
|
||||||
@@ -210,7 +198,7 @@ public class CoreManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_processPre = proc;
|
_processPreService = proc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +213,7 @@ public class CoreManager
|
|||||||
|
|
||||||
#region Process
|
#region Process
|
||||||
|
|
||||||
private async Task<Process?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
private async Task<ProcessService?> RunProcess(CoreInfo? coreInfo, string configPath, bool displayLog, bool mayNeedSudo)
|
||||||
{
|
{
|
||||||
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
var fileName = CoreInfoManager.Instance.GetCoreExecFile(coreInfo, out var msg);
|
||||||
if (fileName.IsNullOrEmpty())
|
if (fileName.IsNullOrEmpty())
|
||||||
@@ -256,55 +244,48 @@ public class CoreManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Process?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
|
private async Task<ProcessService?> RunProcessNormal(string fileName, CoreInfo? coreInfo, string configPath, bool displayLog)
|
||||||
{
|
{
|
||||||
Process proc = new()
|
var environmentVars = new Dictionary<string, string>();
|
||||||
{
|
|
||||||
StartInfo = new()
|
|
||||||
{
|
|
||||||
FileName = fileName,
|
|
||||||
Arguments = string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
|
|
||||||
WorkingDirectory = Utils.GetBinConfigPath(),
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = displayLog,
|
|
||||||
RedirectStandardError = displayLog,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
|
|
||||||
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
foreach (var kv in coreInfo.Environment)
|
foreach (var kv in coreInfo.Environment)
|
||||||
{
|
{
|
||||||
proc.StartInfo.Environment[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
|
environmentVars[kv.Key] = string.Format(kv.Value, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayLog)
|
var procService = new ProcessService(
|
||||||
{
|
fileName: fileName,
|
||||||
void dataHandler(object sender, DataReceivedEventArgs e)
|
arguments: string.Format(coreInfo.Arguments, coreInfo.AbsolutePath ? Utils.GetBinConfigPath(configPath).AppendQuotes() : configPath),
|
||||||
{
|
workingDirectory: Utils.GetBinConfigPath(),
|
||||||
if (e.Data.IsNotEmpty())
|
displayLog: displayLog,
|
||||||
{
|
redirectInput: false,
|
||||||
_ = UpdateFunc(false, e.Data + Environment.NewLine);
|
environmentVars: environmentVars,
|
||||||
}
|
updateFunc: _updateFunc
|
||||||
}
|
);
|
||||||
proc.OutputDataReceived += dataHandler;
|
|
||||||
proc.ErrorDataReceived += dataHandler;
|
|
||||||
}
|
|
||||||
proc.Start();
|
|
||||||
|
|
||||||
if (displayLog)
|
await procService.StartAsync();
|
||||||
{
|
|
||||||
proc.BeginOutputReadLine();
|
|
||||||
proc.BeginErrorReadLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
AppManager.Instance.AddProcess(proc.Handle);
|
|
||||||
if (proc is null or { HasExited: true })
|
if (procService is null or { HasExited: true })
|
||||||
{
|
{
|
||||||
throw new Exception(ResUI.FailedToRunCore);
|
throw new Exception(ResUI.FailedToRunCore);
|
||||||
}
|
}
|
||||||
return proc;
|
AddProcessJob(procService.Handle);
|
||||||
|
|
||||||
|
return procService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddProcessJob(nint processHandle)
|
||||||
|
{
|
||||||
|
if (Utils.IsWindows())
|
||||||
|
{
|
||||||
|
_processJob ??= new();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_processJob?.AddProcess(processHandle);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Process
|
#endregion Process
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class NoticeManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AppEvents.SendSnackMsgRequested.OnNext(content);
|
AppEvents.SendSnackMsgRequested.Publish(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessage(string? content)
|
public void SendMessage(string? content)
|
||||||
@@ -20,7 +20,7 @@ public class NoticeManager
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AppEvents.SendMsgViewRequested.OnNext(content);
|
AppEvents.SendMsgViewRequested.Publish(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendMessageEx(string? content)
|
public void SendMessageEx(string? content)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
|
||||||
namespace ServiceLib.Models;
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
public class CheckUpdateModel
|
public class CheckUpdateModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public bool? IsSelected { get; set; }
|
public bool? IsSelected { get; set; }
|
||||||
public string? CoreType { get; set; }
|
public string? CoreType { get; set; }
|
||||||
public string? Remarks { get; set; }
|
[Reactive] public string? Remarks { get; set; }
|
||||||
public string? FileName { get; set; }
|
public string? FileName { get; set; }
|
||||||
public bool? IsFinished { get; set; }
|
public bool? IsFinished { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
|
||||||
namespace ServiceLib.Models;
|
namespace ServiceLib.Models;
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ClashProxyModel
|
public class ClashProxyModel : ReactiveObject
|
||||||
{
|
{
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
@@ -9,9 +12,9 @@ public class ClashProxyModel
|
|||||||
|
|
||||||
public string? Now { get; set; }
|
public string? Now { get; set; }
|
||||||
|
|
||||||
public int Delay { get; set; }
|
[Reactive] public int Delay { get; set; }
|
||||||
|
|
||||||
public string? DelayName { get; set; }
|
[Reactive] public string? DelayName { get; set; }
|
||||||
|
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ public class SimpleDNSItem
|
|||||||
public bool? UseSystemHosts { get; set; }
|
public bool? UseSystemHosts { get; set; }
|
||||||
public bool? AddCommonHosts { get; set; }
|
public bool? AddCommonHosts { get; set; }
|
||||||
public bool? FakeIP { get; set; }
|
public bool? FakeIP { get; set; }
|
||||||
|
public bool? GlobalFakeIp { get; set; }
|
||||||
public bool? BlockBindingQuery { get; set; }
|
public bool? BlockBindingQuery { get; set; }
|
||||||
public string? DirectDNS { get; set; }
|
public string? DirectDNS { get; set; }
|
||||||
public string? RemoteDNS { get; set; }
|
public string? RemoteDNS { get; set; }
|
||||||
|
|||||||
56
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
56
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
@@ -2301,15 +2301,6 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Apply to Proxy Domains Only 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbApplyProxyDomainsOnly {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbApplyProxyDomainsOnly", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Auto refresh 的本地化字符串。
|
/// 查找类似 Auto refresh 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2526,6 +2517,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Applies globally by default, with built-in FakeIP filtering (sing-box only). 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbFakeIPTips {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbFakeIPTips", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Fingerprint 的本地化字符串。
|
/// 查找类似 Fingerprint 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2913,6 +2913,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Bootstrap DNS (sing-box) 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSBBootstrapDNS {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSBBootstrapDNS", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
|
/// 查找类似 sing-box Direct Resolution Strategy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2923,25 +2932,7 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 The sing-box DoH resolution server can be overwritten 的本地化字符串。
|
/// 查找类似 Resolve DNS server domains, requires IP 的本地化字符串。
|
||||||
/// </summary>
|
|
||||||
public static string TbSBDoHOverride {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSBDoHOverride", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 sing-box DoH Resolver Server 的本地化字符串。
|
|
||||||
/// </summary>
|
|
||||||
public static string TbSBDoHResolverServer {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("TbSBDoHResolverServer", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找类似 Fallback DNS Resolution, Suggest IP 的本地化字符串。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string TbSBFallbackDNSResolve {
|
public static string TbSBFallbackDNSResolve {
|
||||||
get {
|
get {
|
||||||
@@ -3030,6 +3021,15 @@ namespace ServiceLib.Resx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Select Profile 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSelectProfile {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TbSelectProfile", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Set system proxy 的本地化字符串。
|
/// 查找类似 Set system proxy 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1425,11 +1425,11 @@
|
|||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>Resolve Outbound Domains</value>
|
<value>Resolve Outbound Domains</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBBootstrapDNS" xml:space="preserve">
|
||||||
<value>sing-box DoH Resolver Server</value>
|
<value>Bootstrap DNS (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>Fallback DNS Resolution, Suggest IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>xray Freedom Resolution Strategy</value>
|
||||||
@@ -1443,9 +1443,6 @@
|
|||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
|
||||||
<value>The sing-box DoH resolution server can be overwritten</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1455,9 +1452,6 @@
|
|||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
|
||||||
<value>Apply to Proxy Domains Only</value>
|
|
||||||
</data>
|
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Basic DNS Settings</value>
|
<value>Basic DNS Settings</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1512,4 +1506,10 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
|
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1425,11 +1425,11 @@
|
|||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>Resolve Outbound Domains</value>
|
<value>Resolve Outbound Domains</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBBootstrapDNS" xml:space="preserve">
|
||||||
<value>sing-box DoH Resolver Server</value>
|
<value>Bootstrap DNS (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>Fallback DNS Resolution, Suggest IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>xray Freedom Resolution Strategy</value>
|
||||||
@@ -1443,9 +1443,6 @@
|
|||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
|
||||||
<value>The sing-box DoH resolution server can be overwritten</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1455,9 +1452,6 @@
|
|||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
|
||||||
<value>Apply to Proxy Domains Only</value>
|
|
||||||
</data>
|
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Basic DNS Settings</value>
|
<value>Basic DNS Settings</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1512,4 +1506,10 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
|
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1425,11 +1425,11 @@
|
|||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>Resolve Outbound Domains</value>
|
<value>Resolve Outbound Domains</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBBootstrapDNS" xml:space="preserve">
|
||||||
<value>sing-box DoH Resolver Server</value>
|
<value>Bootstrap DNS (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>Fallback DNS Resolution, Suggest IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>xray Freedom Resolution Strategy</value>
|
||||||
@@ -1443,9 +1443,6 @@
|
|||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
|
||||||
<value>The sing-box DoH resolution server can be overwritten</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1455,9 +1452,6 @@
|
|||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
|
||||||
<value>Apply to Proxy Domains Only</value>
|
|
||||||
</data>
|
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Basic DNS Settings</value>
|
<value>Basic DNS Settings</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1512,4 +1506,10 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
|
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1425,11 +1425,11 @@
|
|||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>Разрешать домены для исходящих соединений</value>
|
<value>Разрешать домены для исходящих соединений</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBBootstrapDNS" xml:space="preserve">
|
||||||
<value>Сервер DoH-резолвера (sing-box)</value>
|
<value>Bootstrap DNS (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>Резервное DNS-разрешение (рекомендуется указывать IP)</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>Стратегия резолвинга Freedom (Xray)</value>
|
<value>Стратегия резолвинга Freedom (Xray)</value>
|
||||||
@@ -1443,9 +1443,6 @@
|
|||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Добавить стандартные записи hosts (DNS)</value>
|
<value>Добавить стандартные записи hosts (DNS)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
|
||||||
<value>Сервер DoH-резолвера sing-box можно переопределить</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1455,9 +1452,6 @@
|
|||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS hosts: (каждая строка в формате "domain1 ip1 ip2")</value>
|
<value>DNS hosts: (каждая строка в формате "domain1 ip1 ip2")</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
|
||||||
<value>Применять только к доменам через прокси</value>
|
|
||||||
</data>
|
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Базовые настройки DNS</value>
|
<value>Базовые настройки DNS</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1512,4 +1506,10 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>Start parsing and processing subscription content</value>
|
<value>Start parsing and processing subscription content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
|
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1422,11 +1422,11 @@
|
|||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>解析出站域名</value>
|
<value>解析出站域名</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBBootstrapDNS" xml:space="preserve">
|
||||||
<value>sing-box DoH 解析服务器</value>
|
<value>Bootstrap DNS (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>兜底解析其他 DNS 域名,建议设为 ip</value>
|
<value>解析 DNS 服务器域名,需指定为 IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>xray freedom 解析策略</value>
|
<value>xray freedom 解析策略</value>
|
||||||
@@ -1440,9 +1440,6 @@
|
|||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>添加常用 DNS Hosts</value>
|
<value>添加常用 DNS Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
|
||||||
<value>开启后可覆盖 sing-box DoH 解析服务器</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1452,9 +1449,6 @@
|
|||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts:(“域名1 ip1 ip2” 一行一个)</value>
|
<value>DNS Hosts:(“域名1 ip1 ip2” 一行一个)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
|
||||||
<value>仅对代理域名生效</value>
|
|
||||||
</data>
|
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>DNS 基础设置</value>
|
<value>DNS 基础设置</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1509,4 +1503,10 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>开始解析和处理订阅内容</value>
|
<value>开始解析和处理订阅内容</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>选择配置文件</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
|
<value>默认全局生效,内置 FakeIP 过滤,仅在 sing-box 中生效</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -1422,11 +1422,11 @@
|
|||||||
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
<data name="TbSBOutboundDomainResolve" xml:space="preserve">
|
||||||
<value>Resolve Outbound Domains</value>
|
<value>Resolve Outbound Domains</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHResolverServer" xml:space="preserve">
|
<data name="TbSBBootstrapDNS" xml:space="preserve">
|
||||||
<value>sing-box DoH Resolver Server</value>
|
<value>Bootstrap DNS (sing-box)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
<data name="TbSBFallbackDNSResolve" xml:space="preserve">
|
||||||
<value>Fallback DNS Resolution, Suggest IP</value>
|
<value>Resolve DNS server domains, requires IP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
<data name="TbXrayFreedomResolveStrategy" xml:space="preserve">
|
||||||
<value>xray Freedom Resolution Strategy</value>
|
<value>xray Freedom Resolution Strategy</value>
|
||||||
@@ -1440,9 +1440,6 @@
|
|||||||
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
<data name="TbAddCommonDNSHosts" xml:space="preserve">
|
||||||
<value>Add Common DNS Hosts</value>
|
<value>Add Common DNS Hosts</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbSBDoHOverride" xml:space="preserve">
|
|
||||||
<value>The sing-box DoH resolution server can be overwritten</value>
|
|
||||||
</data>
|
|
||||||
<data name="TbFakeIP" xml:space="preserve">
|
<data name="TbFakeIP" xml:space="preserve">
|
||||||
<value>FakeIP</value>
|
<value>FakeIP</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1452,9 +1449,6 @@
|
|||||||
<data name="TbDNSHostsConfig" xml:space="preserve">
|
<data name="TbDNSHostsConfig" xml:space="preserve">
|
||||||
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
<value>DNS Hosts: ("domain1 ip1 ip2" per line)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TbApplyProxyDomainsOnly" xml:space="preserve">
|
|
||||||
<value>Apply to Proxy Domains Only</value>
|
|
||||||
</data>
|
|
||||||
<data name="ThBasicDNSSettings" xml:space="preserve">
|
<data name="ThBasicDNSSettings" xml:space="preserve">
|
||||||
<value>Basic DNS Settings</value>
|
<value>Basic DNS Settings</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1509,4 +1503,10 @@
|
|||||||
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
<data name="MsgStartParsingSubscription" xml:space="preserve">
|
||||||
<value>開始解析和處理訂閱內容</value>
|
<value>開始解析和處理訂閱內容</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSelectProfile" xml:space="preserve">
|
||||||
|
<value>Select Profile</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbFakeIPTips" xml:space="preserve">
|
||||||
|
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
File diff suppressed because it is too large
Load Diff
92
v2rayN/ServiceLib/Sample/singbox_fakeip_filter
Normal file
92
v2rayN/ServiceLib/Sample/singbox_fakeip_filter
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"domain": [
|
||||||
|
"amobile.music.tc.qq.com",
|
||||||
|
"api-jooxtt.sanook.com",
|
||||||
|
"api.joox.com",
|
||||||
|
"aqqmusic.tc.qq.com",
|
||||||
|
"dl.stream.qqmusic.qq.com",
|
||||||
|
"ff.dorado.sdo.com",
|
||||||
|
"heartbeat.belkin.com",
|
||||||
|
"isure.stream.qqmusic.qq.com",
|
||||||
|
"joox.com",
|
||||||
|
"lens.l.google.com",
|
||||||
|
"localhost.ptlogin2.qq.com",
|
||||||
|
"localhost.sec.qq.com",
|
||||||
|
"mesu.apple.com",
|
||||||
|
"mobileoc.music.tc.qq.com",
|
||||||
|
"music.taihe.com",
|
||||||
|
"musicapi.taihe.com",
|
||||||
|
"na.b.g-tun.com",
|
||||||
|
"proxy.golang.org",
|
||||||
|
"ps.res.netease.com",
|
||||||
|
"shark007.net",
|
||||||
|
"songsearch.kugou.com",
|
||||||
|
"static.adtidy.org",
|
||||||
|
"streamoc.music.tc.qq.com",
|
||||||
|
"swcdn.apple.com",
|
||||||
|
"swdist.apple.com",
|
||||||
|
"swdownload.apple.com",
|
||||||
|
"swquery.apple.com",
|
||||||
|
"swscan.apple.com",
|
||||||
|
"trackercdn.kugou.com",
|
||||||
|
"xnotify.xboxlive.com"
|
||||||
|
],
|
||||||
|
"domain_keyword": [
|
||||||
|
"ntp",
|
||||||
|
"stun",
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"domain_regex": [
|
||||||
|
"^[^.]+$",
|
||||||
|
"^[^.]+\\.[^.]+\\.xboxlive\\.com$",
|
||||||
|
"^localhost\\.[^.]+\\.weixin\\.qq\\.com$",
|
||||||
|
"^mijia\\scloud$",
|
||||||
|
"^xbox\\.[^.]+\\.microsoft\\.com$",
|
||||||
|
"^xbox\\.[^.]+\\.[^.]+\\.microsoft\\.com$"
|
||||||
|
],
|
||||||
|
"domain_suffix": [
|
||||||
|
"126.net",
|
||||||
|
"3gppnetwork.org",
|
||||||
|
"battle.net",
|
||||||
|
"battlenet.com.cn",
|
||||||
|
"cdn.nintendo.net",
|
||||||
|
"cmbchina.com",
|
||||||
|
"cmbimg.com",
|
||||||
|
"ff14.sdo.com",
|
||||||
|
"ffxiv.com",
|
||||||
|
"finalfantasyxiv.com",
|
||||||
|
"gcloudcs.com",
|
||||||
|
"home.arpa",
|
||||||
|
"invalid",
|
||||||
|
"kuwo.cn",
|
||||||
|
"lan",
|
||||||
|
"linksys.com",
|
||||||
|
"linksyssmartwifi.com",
|
||||||
|
"local",
|
||||||
|
"localdomain",
|
||||||
|
"localhost",
|
||||||
|
"market.xiaomi.com",
|
||||||
|
"mcdn.bilivideo.cn",
|
||||||
|
"media.dssott.com",
|
||||||
|
"msftconnecttest.com",
|
||||||
|
"msftncsi.com",
|
||||||
|
"music.163.com",
|
||||||
|
"music.migu.cn",
|
||||||
|
"n0808.com",
|
||||||
|
"nflxvideo.net",
|
||||||
|
"oray.com",
|
||||||
|
"orayimg.com",
|
||||||
|
"router.asus.com",
|
||||||
|
"sandai.net",
|
||||||
|
"square-enix.com",
|
||||||
|
"srv.nintendo.net",
|
||||||
|
"steamcontent.com",
|
||||||
|
"uu.163.com",
|
||||||
|
"wargaming.net",
|
||||||
|
"wggames.cn",
|
||||||
|
"wotgame.cn",
|
||||||
|
"wowsgame.cn",
|
||||||
|
"xiami.com",
|
||||||
|
"y.qq.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="ReactiveUI.Fody" />
|
<PackageReference Include="ReactiveUI.Fody" />
|
||||||
<PackageReference Include="sqlite-net-pcl" />
|
<PackageReference Include="sqlite-net-pcl" />
|
||||||
<PackageReference Include="Splat.NLog" />
|
<PackageReference Include="NLog" />
|
||||||
<PackageReference Include="WebDav.Client" />
|
<PackageReference Include="WebDav.Client" />
|
||||||
<PackageReference Include="YamlDotNet" />
|
<PackageReference Include="YamlDotNet" />
|
||||||
<PackageReference Include="QRCoder" />
|
<PackageReference Include="QRCoder" />
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
<EmbeddedResource Include="Sample\tun_singbox_inbound" />
|
<EmbeddedResource Include="Sample\tun_singbox_inbound" />
|
||||||
<EmbeddedResource Include="Sample\tun_singbox_rules" />
|
<EmbeddedResource Include="Sample\tun_singbox_rules" />
|
||||||
<EmbeddedResource Include="Sample\linux_autostart_config" />
|
<EmbeddedResource Include="Sample\linux_autostart_config" />
|
||||||
|
<EmbeddedResource Include="Sample\singbox_fakeip_filter" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public class CoreConfigClashService
|
|||||||
|
|
||||||
//external-controller
|
//external-controller
|
||||||
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
fileContent["external-controller"] = $"{Global.Loopback}:{AppManager.Instance.StatePort2}";
|
||||||
|
fileContent.Remove("secret");
|
||||||
//allow-lan
|
//allow-lan
|
||||||
if (_config.Inbound.First().AllowLANConn)
|
if (_config.Inbound.First().AllowLANConn)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,17 +33,17 @@ public partial class CoreConfigSingboxService
|
|||||||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||||
}
|
}
|
||||||
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag;
|
||||||
|
if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
||||||
// Tun2SocksAddress
|
|
||||||
if (node != null && Utils.IsDomain(node.Address))
|
|
||||||
{
|
{
|
||||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
singboxConfig.dns.rules.Add(new()
|
||||||
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
|
|
||||||
{
|
{
|
||||||
server = Global.SingboxOutboundResolverTag,
|
server = Global.SingboxFakeDNSTag,
|
||||||
domain = [node.Address],
|
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||||
|
rewrite_ttl = 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await GenOutboundDnsRule(node, singboxConfig, Global.SingboxOutboundResolverTag);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -94,17 +94,7 @@ public partial class CoreConfigSingboxService
|
|||||||
|
|
||||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
var userHostsMap = simpleDNSItem.Hosts
|
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(line => line.Trim())
|
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
|
||||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
.Where(parts => parts.Length >= 2)
|
|
||||||
.GroupBy(parts => parts[0])
|
|
||||||
.ToDictionary(
|
|
||||||
group => group.Key,
|
|
||||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var kvp in userHostsMap)
|
foreach (var kvp in userHostsMap)
|
||||||
{
|
{
|
||||||
@@ -197,6 +187,28 @@ public partial class CoreConfigSingboxService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true)
|
||||||
|
{
|
||||||
|
var fakeipFilterRule = JsonUtils.Deserialize<Rule4Sbox>(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName));
|
||||||
|
fakeipFilterRule.invert = true;
|
||||||
|
var rule4Fake = new Rule4Sbox
|
||||||
|
{
|
||||||
|
server = Global.SingboxFakeDNSTag,
|
||||||
|
type = "logical",
|
||||||
|
mode = "and",
|
||||||
|
rewrite_ttl = 1,
|
||||||
|
rules = new List<Rule4Sbox>
|
||||||
|
{
|
||||||
|
new() {
|
||||||
|
query_type = new List<int> { 1, 28 }, // A and AAAA
|
||||||
|
},
|
||||||
|
fakeipFilterRule,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
singboxConfig.dns.rules.Add(rule4Fake);
|
||||||
|
}
|
||||||
|
|
||||||
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
var routing = await ConfigHandler.GetDefaultRouting(_config);
|
||||||
if (routing == null)
|
if (routing == null)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -276,10 +288,12 @@ public partial class CoreConfigSingboxService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (simpleDNSItem.FakeIP == true)
|
if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false)
|
||||||
{
|
{
|
||||||
var rule4Fake = JsonUtils.DeepCopy(rule);
|
var rule4Fake = JsonUtils.DeepCopy(rule);
|
||||||
rule4Fake.server = Global.SingboxFakeDNSTag;
|
rule4Fake.server = Global.SingboxFakeDNSTag;
|
||||||
|
rule4Fake.query_type = new List<int> { 1, 28 }; // A and AAAA
|
||||||
|
rule4Fake.rewrite_ttl = 1;
|
||||||
singboxConfig.dns.rules.Add(rule4Fake);
|
singboxConfig.dns.rules.Add(rule4Fake);
|
||||||
}
|
}
|
||||||
rule.server = Global.SingboxRemoteDNSTag;
|
rule.server = Global.SingboxRemoteDNSTag;
|
||||||
@@ -323,16 +337,7 @@ public partial class CoreConfigSingboxService
|
|||||||
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
|
await GenDnsDomainsLegacyCompatible(singboxConfig, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tun2SocksAddress
|
await GenOutboundDnsRule(node, singboxConfig, Global.SingboxFinalResolverTag);
|
||||||
if (node != null && Utils.IsDomain(node.Address))
|
|
||||||
{
|
|
||||||
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
|
||||||
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
|
|
||||||
{
|
|
||||||
server = Global.SingboxFinalResolverTag,
|
|
||||||
domain = [node.Address],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -402,6 +407,37 @@ public partial class CoreConfigSingboxService
|
|||||||
return await Task.FromResult(0);
|
return await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig, string? server)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var domain = string.Empty;
|
||||||
|
if (Utils.IsDomain(node.Address)) // normal outbound
|
||||||
|
{
|
||||||
|
domain = node.Address;
|
||||||
|
}
|
||||||
|
else if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty() && Utils.IsDomain(node.SpiderX)) // Tun2SocksAddress
|
||||||
|
{
|
||||||
|
domain = node.SpiderX;
|
||||||
|
}
|
||||||
|
if (domain.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
singboxConfig.dns.rules ??= new List<Rule4Sbox>();
|
||||||
|
singboxConfig.dns.rules.Insert(0, new Rule4Sbox
|
||||||
|
{
|
||||||
|
server = server,
|
||||||
|
domain = [domain],
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
private static Server4Sbox? ParseDnsAddress(string address)
|
private static Server4Sbox? ParseDnsAddress(string address)
|
||||||
{
|
{
|
||||||
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
|
var addressFirst = address?.Split(address.Contains(',') ? ',' : ';').FirstOrDefault()?.Trim();
|
||||||
|
|||||||
@@ -71,6 +71,37 @@ public partial class CoreConfigSingboxService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hostsDomains = new List<string>();
|
||||||
|
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
|
||||||
|
if (dnsItem == null || dnsItem.Enabled == false)
|
||||||
|
{
|
||||||
|
var simpleDNSItem = _config.SimpleDNSItem;
|
||||||
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||||
|
foreach (var kvp in userHostsMap)
|
||||||
|
{
|
||||||
|
hostsDomains.Add(kvp.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (simpleDNSItem.UseSystemHosts == true)
|
||||||
|
{
|
||||||
|
var systemHostsMap = Utils.GetSystemHosts();
|
||||||
|
foreach (var kvp in systemHostsMap)
|
||||||
|
{
|
||||||
|
hostsDomains.Add(kvp.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hostsDomains.Count > 0)
|
||||||
|
{
|
||||||
|
singboxConfig.route.rules.Add(new()
|
||||||
|
{
|
||||||
|
action = "resolve",
|
||||||
|
domain = hostsDomains,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
singboxConfig.route.rules.Add(new()
|
singboxConfig.route.rules.Add(new()
|
||||||
{
|
{
|
||||||
outbound = Global.DirectTag,
|
outbound = Global.DirectTag,
|
||||||
@@ -343,6 +374,13 @@ public partial class CoreConfigSingboxService
|
|||||||
return Global.ProxyTag;
|
return Global.ProxyTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tag = Global.ProxyTag + node.IndexId.ToString();
|
||||||
|
if (singboxConfig.outbounds.Any(o => o.tag == tag)
|
||||||
|
|| (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag)))
|
||||||
|
{
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
var server = await GenServer(node);
|
var server = await GenServer(node);
|
||||||
if (server is null)
|
if (server is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ public partial class CoreConfigV2rayService(Config config)
|
|||||||
|
|
||||||
ret.Success = true;
|
ret.Success = true;
|
||||||
|
|
||||||
ret.Data = await ApplyFullConfigTemplate(v2rayConfig, true);
|
ret.Data = await ApplyFullConfigTemplate(v2rayConfig);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace ServiceLib.Services.CoreConfig;
|
|||||||
|
|
||||||
public partial class CoreConfigV2rayService
|
public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig, bool handleBalancerAndRules = false)
|
private async Task<string> ApplyFullConfigTemplate(V2rayConfig v2rayConfig)
|
||||||
{
|
{
|
||||||
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray);
|
||||||
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
|
if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty())
|
||||||
@@ -19,7 +19,7 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle balancer and rules modifications (for multiple load scenarios)
|
// Handle balancer and rules modifications (for multiple load scenarios)
|
||||||
if (handleBalancerAndRules && v2rayConfig.routing?.balancers?.Count > 0)
|
if (v2rayConfig.routing?.balancers?.Count > 0)
|
||||||
{
|
{
|
||||||
var balancer = v2rayConfig.routing.balancers.First();
|
var balancer = v2rayConfig.routing.balancers.First();
|
||||||
|
|
||||||
@@ -60,6 +60,34 @@ public partial class CoreConfigV2rayService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (v2rayConfig.observatory != null)
|
||||||
|
{
|
||||||
|
if (fullConfigTemplateNode["observatory"] == null)
|
||||||
|
{
|
||||||
|
fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var subjectSelector = v2rayConfig.observatory.subjectSelector;
|
||||||
|
subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||||
|
fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2rayConfig.burstObservatory != null)
|
||||||
|
{
|
||||||
|
if (fullConfigTemplateNode["burstObservatory"] == null)
|
||||||
|
{
|
||||||
|
fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var subjectSelector = v2rayConfig.burstObservatory.subjectSelector;
|
||||||
|
subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue<string>()) ?? []);
|
||||||
|
fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle outbounds - append instead of override
|
// Handle outbounds - append instead of override
|
||||||
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray();
|
||||||
foreach (var outbound in v2rayConfig.outbounds)
|
foreach (var outbound in v2rayConfig.outbounds)
|
||||||
|
|||||||
@@ -261,17 +261,7 @@ public partial class CoreConfigV2rayService
|
|||||||
|
|
||||||
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
var userHostsMap = simpleDNSItem.Hosts
|
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
|
||||||
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(line => line.Trim())
|
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
|
|
||||||
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
.Where(parts => parts.Length >= 2)
|
|
||||||
.GroupBy(parts => parts[0])
|
|
||||||
.ToDictionary(
|
|
||||||
group => group.Key,
|
|
||||||
group => group.SelectMany(parts => parts.Skip(1)).ToList()
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var kvp in userHostsMap)
|
foreach (var kvp in userHostsMap)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -131,10 +131,16 @@ public partial class CoreConfigV2rayService
|
|||||||
return Global.ProxyTag;
|
return Global.ProxyTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tag = Global.ProxyTag + node.IndexId.ToString();
|
||||||
|
if (v2rayConfig.outbounds.Any(p => p.tag == tag))
|
||||||
|
{
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound);
|
||||||
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var outbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
await GenOutbound(node, outbound);
|
await GenOutbound(node, outbound);
|
||||||
outbound.tag = Global.ProxyTag + node.IndexId.ToString();
|
outbound.tag = tag;
|
||||||
v2rayConfig.outbounds.Add(outbound);
|
v2rayConfig.outbounds.Add(outbound);
|
||||||
|
|
||||||
return outbound.tag;
|
return outbound.tag;
|
||||||
|
|||||||
183
v2rayN/ServiceLib/Services/ProcessService.cs
Normal file
183
v2rayN/ServiceLib/Services/ProcessService.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ServiceLib.Services;
|
||||||
|
|
||||||
|
public class ProcessService : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Process _process;
|
||||||
|
private readonly Func<bool, string, Task>? _updateFunc;
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
public int Id => _process.Id;
|
||||||
|
public IntPtr Handle => _process.Handle;
|
||||||
|
public bool HasExited => _process.HasExited;
|
||||||
|
|
||||||
|
public ProcessService(
|
||||||
|
string fileName,
|
||||||
|
string arguments,
|
||||||
|
string workingDirectory,
|
||||||
|
bool displayLog,
|
||||||
|
bool redirectInput,
|
||||||
|
Dictionary<string, string>? environmentVars,
|
||||||
|
Func<bool, string, Task>? updateFunc)
|
||||||
|
{
|
||||||
|
_updateFunc = updateFunc;
|
||||||
|
|
||||||
|
_process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
Arguments = arguments,
|
||||||
|
WorkingDirectory = workingDirectory,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardInput = redirectInput,
|
||||||
|
RedirectStandardOutput = displayLog,
|
||||||
|
RedirectStandardError = displayLog,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
StandardOutputEncoding = displayLog ? Encoding.UTF8 : null,
|
||||||
|
StandardErrorEncoding = displayLog ? Encoding.UTF8 : null,
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (environmentVars != null)
|
||||||
|
{
|
||||||
|
foreach (var kv in environmentVars)
|
||||||
|
{
|
||||||
|
_process.StartInfo.Environment[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayLog)
|
||||||
|
{
|
||||||
|
RegisterEventHandlers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(string pwd = null)
|
||||||
|
{
|
||||||
|
_process.Start();
|
||||||
|
|
||||||
|
if (_process.StartInfo.RedirectStandardOutput)
|
||||||
|
{
|
||||||
|
_process.BeginOutputReadLine();
|
||||||
|
_process.BeginErrorReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_process.StartInfo.RedirectStandardInput)
|
||||||
|
{
|
||||||
|
await Task.Delay(10);
|
||||||
|
await _process.StandardInput.WriteLineAsync(pwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync()
|
||||||
|
{
|
||||||
|
if (_process.HasExited)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_process.StartInfo.RedirectStandardOutput)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_process.CancelOutputRead();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_process.CancelErrorRead();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Utils.IsNonWindows())
|
||||||
|
{
|
||||||
|
_process.Kill(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_process.Kill();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _updateFunc?.Invoke(true, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterEventHandlers()
|
||||||
|
{
|
||||||
|
void dataHandler(object sender, DataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Data.IsNotEmpty())
|
||||||
|
{
|
||||||
|
_ = _updateFunc?.Invoke(false, e.Data + Environment.NewLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_process.OutputDataReceived += dataHandler;
|
||||||
|
_process.ErrorDataReceived += dataHandler;
|
||||||
|
|
||||||
|
_process.Exited += (s, e) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_process.OutputDataReceived -= dataHandler;
|
||||||
|
_process.ErrorDataReceived -= dataHandler;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_process.HasExited)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_process.CancelOutputRead();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_process.CancelErrorRead();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
_process.Kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
_process.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_updateFunc?.Invoke(true, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -182,11 +182,11 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
|
|
||||||
private async Task<bool> RunRealPingAsync(List<ServerTestItem> selecteds, string exitLoopKey)
|
private async Task<bool> RunRealPingAsync(List<ServerTestItem> selecteds, string exitLoopKey)
|
||||||
{
|
{
|
||||||
var pid = -1;
|
ProcessService processService = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds);
|
processService = await CoreManager.Instance.LoadCoreConfigSpeedtest(selecteds);
|
||||||
if (pid < 0)
|
if (processService is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -216,10 +216,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (pid > 0)
|
await processService?.StopAsync();
|
||||||
{
|
|
||||||
await ProcUtils.ProcessKill(pid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -244,11 +241,11 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
|
|
||||||
tasks.Add(Task.Run(async () =>
|
tasks.Add(Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var pid = -1;
|
ProcessService processService = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pid = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
|
processService = await CoreManager.Instance.LoadCoreConfigSpeedtest(it);
|
||||||
if (pid < 0)
|
if (processService is null)
|
||||||
{
|
{
|
||||||
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
await UpdateFunc(it.IndexId, "", ResUI.FailedToRunCore);
|
||||||
}
|
}
|
||||||
@@ -275,10 +272,7 @@ public class SpeedtestService(Config config, Func<SpeedTestResult, Task> updateF
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (pid > 0)
|
await processService?.StopAsync();
|
||||||
{
|
|
||||||
await ProcUtils.ProcessKill(pid);
|
|
||||||
}
|
|
||||||
concurrencySemaphore.Release();
|
concurrencySemaphore.Release();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ using System.Reactive;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using DynamicData;
|
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
@@ -38,7 +36,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.EnableCheckPreReleaseUpdate,
|
x => x.EnableCheckPreReleaseUpdate,
|
||||||
y => y == true)
|
y => y == true)
|
||||||
.Subscribe(c => { _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate; });
|
.Subscribe(c => _config.CheckUpdateItem.CheckPreReleaseUpdate = EnableCheckPreReleaseUpdate);
|
||||||
|
|
||||||
RefreshCheckUpdateItems();
|
RefreshCheckUpdateItems();
|
||||||
}
|
}
|
||||||
@@ -63,6 +61,16 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
private CheckUpdateModel GetCheckUpdateModel(string coreType)
|
||||||
{
|
{
|
||||||
|
if (coreType == _v2rayN && Utils.IsPackagedInstall())
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
IsSelected = false,
|
||||||
|
CoreType = coreType,
|
||||||
|
Remarks = ResUI.menuCheckUpdate + " (Not Support)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true,
|
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true,
|
||||||
@@ -104,6 +112,11 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else if (item.CoreType == _v2rayN)
|
else if (item.CoreType == _v2rayN)
|
||||||
{
|
{
|
||||||
|
if (Utils.IsPackagedInstall())
|
||||||
|
{
|
||||||
|
await UpdateView(_v2rayN, "Not Support");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await CheckUpdateN(EnableCheckPreReleaseUpdate);
|
await CheckUpdateN(EnableCheckPreReleaseUpdate);
|
||||||
}
|
}
|
||||||
else if (item.CoreType == ECoreType.Xray.ToString())
|
else if (item.CoreType == ECoreType.Xray.ToString())
|
||||||
@@ -143,11 +156,8 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
UpdatedPlusPlus(_geo, "");
|
UpdatedPlusPlus(_geo, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await (new UpdateService()).UpdateGeoFileAll(_config, _updateUI)
|
await new UpdateService().UpdateGeoFileAll(_config, _updateUI)
|
||||||
.ContinueWith(t =>
|
.ContinueWith(t => UpdatedPlusPlus(_geo, ""));
|
||||||
{
|
|
||||||
UpdatedPlusPlus(_geo, "");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckUpdateN(bool preRelease)
|
private async Task CheckUpdateN(bool preRelease)
|
||||||
@@ -161,11 +171,8 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
UpdatedPlusPlus(_v2rayN, msg);
|
UpdatedPlusPlus(_v2rayN, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await (new UpdateService()).CheckUpdateGuiN(_config, _updateUI, preRelease)
|
await new UpdateService().CheckUpdateGuiN(_config, _updateUI, preRelease)
|
||||||
.ContinueWith(t =>
|
.ContinueWith(t => UpdatedPlusPlus(_v2rayN, ""));
|
||||||
{
|
|
||||||
UpdatedPlusPlus(_v2rayN, "");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
|
private async Task CheckUpdateCore(CheckUpdateModel model, bool preRelease)
|
||||||
@@ -181,11 +188,8 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType);
|
var type = (ECoreType)Enum.Parse(typeof(ECoreType), model.CoreType);
|
||||||
await (new UpdateService()).CheckUpdateCore(type, _config, _updateUI, preRelease)
|
await new UpdateService().CheckUpdateCore(type, _config, _updateUI, preRelease)
|
||||||
.ContinueWith(t =>
|
.ContinueWith(t => UpdatedPlusPlus(model.CoreType, ""));
|
||||||
{
|
|
||||||
UpdatedPlusPlus(model.CoreType, "");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateFinished()
|
private async Task UpdateFinished()
|
||||||
@@ -219,11 +223,11 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
if (blReload)
|
if (blReload)
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
AppEvents.ReloadRequested.Publish();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.CloseCore();
|
await CoreManager.Instance.CoreStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +300,7 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
|
|
||||||
if (Utils.IsNonWindows())
|
if (Utils.IsNonWindows())
|
||||||
{
|
{
|
||||||
var filesList = (new DirectoryInfo(toPath)).GetFiles().Select(u => u.FullName).ToList();
|
var filesList = new DirectoryInfo(toPath).GetFiles().Select(u => u.FullName).ToList();
|
||||||
foreach (var file in filesList)
|
foreach (var file in filesList)
|
||||||
{
|
{
|
||||||
await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower()));
|
await Utils.SetLinuxChmod(Path.Combine(toPath, item.CoreType.ToLower()));
|
||||||
@@ -334,9 +338,6 @@ public class CheckUpdateViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
found.Remarks = model.Remarks;
|
||||||
var itemCopy = JsonUtils.DeepCopy(found);
|
|
||||||
itemCopy.Remarks = model.Remarks;
|
|
||||||
CheckUpdateModels.Replace(found, itemCopy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
SortingSelected = _config.ClashUIItem.ProxiesSorting;
|
SortingSelected = _config.ClashUIItem.ProxiesSorting;
|
||||||
RuleModeSelected = (int)_config.ClashUIItem.RuleMode;
|
RuleModeSelected = (int)_config.ClashUIItem.RuleMode;
|
||||||
|
|
||||||
|
#region WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.SelectedGroup,
|
x => x.SelectedGroup,
|
||||||
y => y != null && y.Name.IsNotEmpty())
|
y => y != null && y.Name.IsNotEmpty())
|
||||||
@@ -89,6 +91,17 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
y => y == true)
|
y => y == true)
|
||||||
.Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; });
|
.Subscribe(c => { _config.ClashUIItem.ProxiesAutoRefresh = AutoRefresh; });
|
||||||
|
|
||||||
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
#region AppEvents
|
||||||
|
|
||||||
|
AppEvents.ProxiesReloadRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await ProxiesReload());
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +404,6 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task ProxiesDelayTestResult(SpeedTestResult result)
|
public async Task ProxiesDelayTestResult(SpeedTestResult result)
|
||||||
{
|
{
|
||||||
//UpdateHandler(false, $"{item.name}={result}");
|
|
||||||
var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
|
var detail = ProxyDetails.FirstOrDefault(it => it.Name == result.IndexId);
|
||||||
if (detail == null)
|
if (detail == null)
|
||||||
{
|
{
|
||||||
@@ -414,7 +426,6 @@ public class ClashProxiesViewModel : MyReactiveObject
|
|||||||
detail.Delay = _delayTimeout;
|
detail.Delay = _delayTimeout;
|
||||||
detail.DelayName = string.Empty;
|
detail.DelayName = string.Empty;
|
||||||
}
|
}
|
||||||
ProxyDetails.Replace(detail, JsonUtils.DeepCopy(detail));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion proxy function
|
#endregion proxy function
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
|
||||||
@@ -32,6 +33,8 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
[Reactive] public bool RayCustomDNSEnableCompatible { get; set; }
|
||||||
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
[Reactive] public bool SBCustomDNSEnableCompatible { get; set; }
|
||||||
|
|
||||||
|
[ObservableAsProperty] public bool IsSimpleDNSEnabled { get; }
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
public ReactiveCommand<Unit, Unit> SaveCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
public ReactiveCommand<Unit, Unit> ImportDefConfig4V2rayCompatibleCmd { get; }
|
||||||
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
public ReactiveCommand<Unit, Unit> ImportDefConfig4SingboxCompatibleCmd { get; }
|
||||||
@@ -55,6 +58,10 @@ public class DNSSettingViewModel : MyReactiveObject
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.RayCustomDNSEnableCompatible, x => x.SBCustomDNSEnableCompatible)
|
||||||
|
.Select(x => !(x.Item1 && x.Item2))
|
||||||
|
.ToPropertyEx(this, x => x.IsSimpleDNSEnabled);
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Concurrency;
|
using System.Reactive.Concurrency;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
});
|
});
|
||||||
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
|
RebootAsAdminCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
await RebootAsAdmin();
|
await AppManager.Instance.RebootAsAdmin();
|
||||||
});
|
});
|
||||||
ClearServerStatisticsCmd = ReactiveCommand.CreateFromTask(async () =>
|
ClearServerStatisticsCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
@@ -217,6 +217,30 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#endregion WhenAnyValue && ReactiveCommand
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
#region AppEvents
|
||||||
|
|
||||||
|
AppEvents.ReloadRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await Reload());
|
||||||
|
|
||||||
|
AppEvents.AddServerViaScanRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await AddServerViaScanAsync());
|
||||||
|
|
||||||
|
AppEvents.AddServerViaClipboardRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await AddServerViaClipboardAsync(null));
|
||||||
|
|
||||||
|
AppEvents.SubscriptionsUpdateRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async blProxy => await UpdateSubscriptionProcess("", blProxy));
|
||||||
|
|
||||||
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +248,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
_config.UiItem.ShowInTaskbar = true;
|
_config.UiItem.ShowInTaskbar = true;
|
||||||
|
|
||||||
await ConfigHandler.InitBuiltinRouting(_config);
|
//await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
await ConfigHandler.InitBuiltinDNS(_config);
|
await ConfigHandler.InitBuiltinDNS(_config);
|
||||||
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
|
||||||
await ProfileExManager.Instance.Init();
|
await ProfileExManager.Instance.Init();
|
||||||
@@ -235,11 +259,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
await StatisticsManager.Instance.Init(_config, UpdateStatisticsHandler);
|
||||||
}
|
}
|
||||||
|
await RefreshServers();
|
||||||
|
|
||||||
BlReloadEnabled = true;
|
BlReloadEnabled = true;
|
||||||
await Reload();
|
await Reload();
|
||||||
await AutoHideStartup();
|
await AutoHideStartup();
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Init
|
#endregion Init
|
||||||
@@ -268,7 +292,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
if (_config.UiItem.EnableAutoAdjustMainLvColWidth)
|
||||||
{
|
{
|
||||||
AppEvents.AdjustMainLvColWidthRequested.OnNext(Unit.Default);
|
AppEvents.AdjustMainLvColWidthRequested.Publish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,12 +303,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AppEvents.DispatcherStatisticsRequested.OnNext(update);
|
AppEvents.DispatcherStatisticsRequested.Publish(update);
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowHideWindow(bool? blShow)
|
|
||||||
{
|
|
||||||
_updateView?.Invoke(EViewAction.ShowHideWindow, blShow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Actions
|
#endregion Actions
|
||||||
@@ -293,14 +312,14 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task RefreshServers()
|
private async Task RefreshServers()
|
||||||
{
|
{
|
||||||
AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
|
AppEvents.ProfilesRefreshRequested.Publish();
|
||||||
|
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSubscriptions()
|
private void RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<ProfilesViewModel>()?.RefreshSubscriptions();
|
AppEvents.SubscriptionsRefreshRequested.Publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Servers && Groups
|
#endregion Servers && Groups
|
||||||
@@ -432,7 +451,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
|
var ret = await _updateView?.Invoke(EViewAction.OptionSettingWindow, null);
|
||||||
if (ret == true)
|
if (ret == true)
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.InboundDisplayStatus();
|
AppEvents.InboundDisplayRequested.Publish();
|
||||||
await Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,7 +462,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
if (ret == true)
|
if (ret == true)
|
||||||
{
|
{
|
||||||
await ConfigHandler.InitBuiltinRouting(_config);
|
await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||||
await Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,12 +485,6 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RebootAsAdmin()
|
|
||||||
{
|
|
||||||
ProcUtils.RebootAsAdmin();
|
|
||||||
await AppManager.Instance.AppExitAsync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ClearServerStatistics()
|
private async Task ClearServerStatistics()
|
||||||
{
|
{
|
||||||
await StatisticsManager.Instance.ClearAllServerStatistics();
|
await StatisticsManager.Instance.ClearAllServerStatistics();
|
||||||
@@ -487,7 +500,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
else if (Utils.IsLinux())
|
else if (Utils.IsLinux())
|
||||||
{
|
{
|
||||||
ProcUtils.ProcessStart("nautilus", path);
|
ProcUtils.ProcessStart("xdg-open", path);
|
||||||
}
|
}
|
||||||
else if (Utils.IsOSX())
|
else if (Utils.IsOSX())
|
||||||
{
|
{
|
||||||
@@ -517,9 +530,15 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
await SysProxyHandler.UpdateSysProxy(_config, false);
|
await SysProxyHandler.UpdateSysProxy(_config, false);
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
});
|
});
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.TestServerAvailability();
|
AppEvents.TestServerRequested.Publish();
|
||||||
|
|
||||||
RxApp.MainThreadScheduler.Schedule(() => _ = ReloadResult());
|
var showClashUI = _config.IsRunningCore(ECoreType.sing_box);
|
||||||
|
if (showClashUI)
|
||||||
|
{
|
||||||
|
AppEvents.ProxiesReloadRequested.Publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
RxApp.MainThreadScheduler.Schedule(() => ReloadResult(showClashUI));
|
||||||
|
|
||||||
BlReloadEnabled = true;
|
BlReloadEnabled = true;
|
||||||
if (_hasNextReloadJob)
|
if (_hasNextReloadJob)
|
||||||
@@ -529,19 +548,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReloadResult()
|
private void ReloadResult(bool showClashUI)
|
||||||
{
|
{
|
||||||
// BlReloadEnabled = true;
|
// BlReloadEnabled = true;
|
||||||
//Locator.Current.GetService<StatusBarViewModel>()?.ChangeSystemProxyAsync(_config.systemProxyItem.sysProxyType, false);
|
ShowClashUI = showClashUI;
|
||||||
ShowClashUI = _config.IsRunningCore(ECoreType.sing_box);
|
TabMainSelectedIndex = showClashUI ? TabMainSelectedIndex : 0;
|
||||||
if (ShowClashUI)
|
|
||||||
{
|
|
||||||
Locator.Current.GetService<ClashProxiesViewModel>()?.ProxiesReload();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TabMainSelectedIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadCore()
|
private async Task LoadCore()
|
||||||
@@ -550,17 +561,11 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
await CoreManager.Instance.LoadCore(node);
|
await CoreManager.Instance.LoadCore(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CloseCore()
|
|
||||||
{
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
|
||||||
await CoreManager.Instance.CoreStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AutoHideStartup()
|
private async Task AutoHideStartup()
|
||||||
{
|
{
|
||||||
if (_config.UiItem.AutoHideStartup)
|
if (_config.UiItem.AutoHideStartup)
|
||||||
{
|
{
|
||||||
ShowHideWindow(false);
|
AppEvents.ShowHideWindowRequested.Publish(false);
|
||||||
}
|
}
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -573,7 +578,7 @@ public class MainWindowViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
await ConfigHandler.ApplyRegionalPreset(_config, type);
|
await ConfigHandler.ApplyRegionalPreset(_config, type);
|
||||||
await ConfigHandler.InitRouting(_config);
|
await ConfigHandler.InitRouting(_config);
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.RefreshRoutingsMenu();
|
AppEvents.RoutingsMenuRefreshRequested.Publish();
|
||||||
|
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);
|
await new UpdateService().UpdateGeoFileAll(_config, UpdateTaskHandler);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
@@ -9,9 +10,9 @@ namespace ServiceLib.ViewModels;
|
|||||||
public class MsgViewModel : MyReactiveObject
|
public class MsgViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||||
private readonly int _numMaxMsg = 500;
|
private volatile bool _lastMsgFilterNotAvailable;
|
||||||
private bool _lastMsgFilterNotAvailable;
|
private int _showLock = 0; // 0 = unlocked, 1 = locked
|
||||||
private bool _blLockShow = false;
|
public int NumMaxMsg { get; } = 500;
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string MsgFilter { get; set; }
|
public string MsgFilter { get; set; }
|
||||||
@@ -33,46 +34,52 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.AutoRefresh,
|
x => x.AutoRefresh,
|
||||||
y => y == true)
|
y => y == true)
|
||||||
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
|
.Subscribe(c => _config.MsgUIItem.AutoRefresh = AutoRefresh);
|
||||||
|
|
||||||
AppEvents.SendMsgViewRequested
|
AppEvents.SendMsgViewRequested
|
||||||
.AsObservable()
|
.AsObservable()
|
||||||
//.ObserveOn(RxApp.MainThreadScheduler)
|
//.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
.Subscribe(async content => await AppendQueueMsg(content));
|
.Subscribe(content => _ = AppendQueueMsg(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AppendQueueMsg(string msg)
|
private async Task AppendQueueMsg(string msg)
|
||||||
{
|
{
|
||||||
//if (msg == Global.CommandClearMsg)
|
|
||||||
//{
|
|
||||||
// ClearMsg();
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
if (AutoRefresh == false)
|
if (AutoRefresh == false)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ = EnqueueQueueMsg(msg);
|
|
||||||
|
|
||||||
if (_blLockShow)
|
EnqueueQueueMsg(msg);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!_config.UiItem.ShowInTaskbar)
|
if (!_config.UiItem.ShowInTaskbar)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_blLockShow = true;
|
if (Interlocked.CompareExchange(ref _showLock, 1, 0) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await Task.Delay(500);
|
try
|
||||||
var txt = string.Join("", _queueMsg.ToArray());
|
{
|
||||||
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt);
|
await Task.Delay(500).ConfigureAwait(false);
|
||||||
|
|
||||||
_blLockShow = false;
|
var sb = new StringBuilder();
|
||||||
|
while (_queueMsg.TryDequeue(out var line))
|
||||||
|
{
|
||||||
|
sb.Append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _showLock, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnqueueQueueMsg(string msg)
|
private void EnqueueQueueMsg(string msg)
|
||||||
{
|
{
|
||||||
//filter msg
|
//filter msg
|
||||||
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
|
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
|
||||||
@@ -91,26 +98,17 @@ public class MsgViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Enqueue
|
|
||||||
if (_queueMsg.Count > _numMaxMsg)
|
|
||||||
{
|
|
||||||
for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++)
|
|
||||||
{
|
|
||||||
_queueMsg.TryDequeue(out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_queueMsg.Enqueue(msg);
|
_queueMsg.Enqueue(msg);
|
||||||
if (!msg.EndsWith(Environment.NewLine))
|
if (!msg.EndsWith(Environment.NewLine))
|
||||||
{
|
{
|
||||||
_queueMsg.Enqueue(Environment.NewLine);
|
_queueMsg.Enqueue(Environment.NewLine);
|
||||||
}
|
}
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearMsg()
|
//public void ClearMsg()
|
||||||
{
|
//{
|
||||||
_queueMsg.Clear();
|
// _queueMsg.Clear();
|
||||||
}
|
//}
|
||||||
|
|
||||||
private void DoMsgFilter()
|
private void DoMsgFilter()
|
||||||
{
|
{
|
||||||
|
|||||||
352
v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs
Normal file
352
v2rayN/ServiceLib/ViewModels/ProfilesSelectViewModel.cs
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
|
||||||
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
|
public class ProfilesSelectViewModel : MyReactiveObject
|
||||||
|
{
|
||||||
|
#region private prop
|
||||||
|
|
||||||
|
private string _serverFilter = string.Empty;
|
||||||
|
private Dictionary<string, bool> _dicHeaderSort = new();
|
||||||
|
private string _subIndexId = string.Empty;
|
||||||
|
|
||||||
|
// ConfigType filter state: default include-mode with all types selected
|
||||||
|
private List<EConfigType> _filterConfigTypes = new();
|
||||||
|
|
||||||
|
private bool _filterExclude = false;
|
||||||
|
|
||||||
|
#endregion private prop
|
||||||
|
|
||||||
|
#region ObservableCollection
|
||||||
|
|
||||||
|
public IObservableCollection<ProfileItemModel> ProfileItems { get; } = new ObservableCollectionExtended<ProfileItemModel>();
|
||||||
|
|
||||||
|
public IObservableCollection<SubItem> SubItems { get; } = new ObservableCollectionExtended<SubItem>();
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public ProfileItemModel SelectedProfile { get; set; }
|
||||||
|
|
||||||
|
public IList<ProfileItemModel> SelectedProfiles { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public SubItem SelectedSub { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public string ServerFilter { get; set; }
|
||||||
|
|
||||||
|
// Include/Exclude filter for ConfigType
|
||||||
|
public List<EConfigType> FilterConfigTypes
|
||||||
|
{
|
||||||
|
get => _filterConfigTypes;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _filterConfigTypes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public bool FilterExclude
|
||||||
|
{
|
||||||
|
get => _filterExclude;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _filterExclude, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion ObservableCollection
|
||||||
|
|
||||||
|
#region Init
|
||||||
|
|
||||||
|
public ProfilesSelectViewModel(Func<EViewAction, object?, Task<bool>>? updateView)
|
||||||
|
{
|
||||||
|
_config = AppManager.Instance.Config;
|
||||||
|
_updateView = updateView;
|
||||||
|
_subIndexId = _config.SubIndexId ?? string.Empty;
|
||||||
|
|
||||||
|
#region WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
this.WhenAnyValue(
|
||||||
|
x => x.SelectedSub,
|
||||||
|
y => y != null && !y.Remarks.IsNullOrEmpty() && _subIndexId != y.Id)
|
||||||
|
.Subscribe(async c => await SubSelectedChangedAsync(c));
|
||||||
|
|
||||||
|
this.WhenAnyValue(
|
||||||
|
x => x.ServerFilter,
|
||||||
|
y => y != null && _serverFilter != y)
|
||||||
|
.Subscribe(async c => await ServerFilterChanged(c));
|
||||||
|
|
||||||
|
// React to ConfigType filter changes
|
||||||
|
this.WhenAnyValue(x => x.FilterExclude)
|
||||||
|
.Skip(1)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.FilterConfigTypes)
|
||||||
|
.Skip(1)
|
||||||
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
#endregion WhenAnyValue && ReactiveCommand
|
||||||
|
|
||||||
|
_ = Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Init()
|
||||||
|
{
|
||||||
|
SelectedProfile = new();
|
||||||
|
SelectedSub = new();
|
||||||
|
|
||||||
|
// Default: include mode with all ConfigTypes selected
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FilterExclude = false;
|
||||||
|
FilterConfigTypes = Enum.GetValues(typeof(EConfigType)).Cast<EConfigType>().ToList();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
FilterConfigTypes = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
await RefreshSubscriptions();
|
||||||
|
await RefreshServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Init
|
||||||
|
|
||||||
|
#region Actions
|
||||||
|
|
||||||
|
public bool CanOk()
|
||||||
|
{
|
||||||
|
return SelectedProfile != null && !SelectedProfile.IndexId.IsNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SelectFinish()
|
||||||
|
{
|
||||||
|
if (!CanOk())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_updateView?.Invoke(EViewAction.CloseWindow, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Actions
|
||||||
|
|
||||||
|
#region Servers && Groups
|
||||||
|
|
||||||
|
private async Task SubSelectedChangedAsync(bool c)
|
||||||
|
{
|
||||||
|
if (!c)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_subIndexId = SelectedSub?.Id;
|
||||||
|
|
||||||
|
await RefreshServers();
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.ProfilesFocus, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ServerFilterChanged(bool c)
|
||||||
|
{
|
||||||
|
if (!c)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_serverFilter = ServerFilter;
|
||||||
|
if (_serverFilter.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
await RefreshServers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshServers()
|
||||||
|
{
|
||||||
|
await RefreshServersBiz();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshServersBiz()
|
||||||
|
{
|
||||||
|
var lstModel = await GetProfileItemsEx(_subIndexId, _serverFilter);
|
||||||
|
|
||||||
|
ProfileItems.Clear();
|
||||||
|
ProfileItems.AddRange(lstModel);
|
||||||
|
if (lstModel.Count > 0)
|
||||||
|
{
|
||||||
|
var selected = lstModel.FirstOrDefault(t => t.IndexId == _config.IndexId);
|
||||||
|
if (selected != null)
|
||||||
|
{
|
||||||
|
SelectedProfile = selected;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedProfile = lstModel.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshSubscriptions()
|
||||||
|
{
|
||||||
|
SubItems.Clear();
|
||||||
|
|
||||||
|
SubItems.Add(new SubItem { Remarks = ResUI.AllGroupServers });
|
||||||
|
|
||||||
|
foreach (var item in await AppManager.Instance.SubItems())
|
||||||
|
{
|
||||||
|
SubItems.Add(item);
|
||||||
|
}
|
||||||
|
if (_subIndexId != null && SubItems.FirstOrDefault(t => t.Id == _subIndexId) != null)
|
||||||
|
{
|
||||||
|
SelectedSub = SubItems.FirstOrDefault(t => t.Id == _subIndexId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedSub = SubItems.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<ProfileItemModel>?> GetProfileItemsEx(string subid, string filter)
|
||||||
|
{
|
||||||
|
var lstModel = await AppManager.Instance.ProfileItems(_subIndexId, filter);
|
||||||
|
lstModel = (from t in lstModel
|
||||||
|
select new ProfileItemModel
|
||||||
|
{
|
||||||
|
IndexId = t.IndexId,
|
||||||
|
ConfigType = t.ConfigType,
|
||||||
|
Remarks = t.Remarks,
|
||||||
|
Address = t.Address,
|
||||||
|
Port = t.Port,
|
||||||
|
Security = t.Security,
|
||||||
|
Network = t.Network,
|
||||||
|
StreamSecurity = t.StreamSecurity,
|
||||||
|
Subid = t.Subid,
|
||||||
|
SubRemarks = t.SubRemarks,
|
||||||
|
IsActive = t.IndexId == _config.IndexId,
|
||||||
|
}).OrderBy(t => t.Sort).ToList();
|
||||||
|
|
||||||
|
// Apply ConfigType filter (include or exclude)
|
||||||
|
if (FilterConfigTypes != null && FilterConfigTypes.Count > 0)
|
||||||
|
{
|
||||||
|
if (FilterExclude)
|
||||||
|
{
|
||||||
|
lstModel = lstModel.Where(t => !FilterConfigTypes.Contains(t.ConfigType)).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lstModel = lstModel.Where(t => FilterConfigTypes.Contains(t.ConfigType)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lstModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProfileItem?> GetProfileItem()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SelectedProfile?.IndexId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var indexId = SelectedProfile.IndexId;
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(indexId);
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ProfileItem>?> GetProfileItems()
|
||||||
|
{
|
||||||
|
if (SelectedProfiles == null || SelectedProfiles.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var lst = new List<ProfileItem>();
|
||||||
|
foreach (var sp in SelectedProfiles)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sp?.IndexId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var item = await AppManager.Instance.GetProfileItem(sp.IndexId);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
lst.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lst.Count == 0)
|
||||||
|
{
|
||||||
|
NoticeManager.Instance.Enqueue(ResUI.PleaseSelectServer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SortServer(string colName)
|
||||||
|
{
|
||||||
|
if (colName.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prop = typeof(ProfileItemModel).GetProperty(colName);
|
||||||
|
if (prop == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dicHeaderSort.TryAdd(colName, true);
|
||||||
|
var asc = _dicHeaderSort[colName];
|
||||||
|
|
||||||
|
var comparer = Comparer<object?>.Create((a, b) =>
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(a, b))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a is null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b is null)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.GetType() == b.GetType() && a is IComparable ca)
|
||||||
|
{
|
||||||
|
return ca.CompareTo(b);
|
||||||
|
}
|
||||||
|
return string.Compare(a.ToString(), b.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
});
|
||||||
|
|
||||||
|
object? KeySelector(ProfileItemModel x)
|
||||||
|
{
|
||||||
|
return prop.GetValue(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<ProfileItemModel> sorted = asc
|
||||||
|
? ProfileItems.OrderBy(KeySelector, comparer)
|
||||||
|
: ProfileItems.OrderByDescending(KeySelector, comparer);
|
||||||
|
|
||||||
|
var list = sorted.ToList();
|
||||||
|
ProfileItems.Clear();
|
||||||
|
ProfileItems.AddRange(list);
|
||||||
|
|
||||||
|
_dicHeaderSort[colName] = !asc;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Servers && Groups
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
// External setter for ConfigType filter
|
||||||
|
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
|
||||||
|
{
|
||||||
|
FilterConfigTypes = types?.Distinct().ToList() ?? new List<EConfigType>();
|
||||||
|
FilterExclude = exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public API
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ using DynamicData;
|
|||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
@@ -38,15 +37,9 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public SubItem SelectedMoveToGroup { get; set; }
|
public SubItem SelectedMoveToGroup { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public ComboItem SelectedServer { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public string ServerFilter { get; set; }
|
public string ServerFilter { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public bool BlServers { get; set; }
|
|
||||||
|
|
||||||
#endregion ObservableCollection
|
#endregion ObservableCollection
|
||||||
|
|
||||||
#region Menu
|
#region Menu
|
||||||
@@ -115,11 +108,6 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
y => y != null && !y.Remarks.IsNullOrEmpty())
|
y => y != null && !y.Remarks.IsNullOrEmpty())
|
||||||
.Subscribe(async c => await MoveToGroup(c));
|
.Subscribe(async c => await MoveToGroup(c));
|
||||||
|
|
||||||
this.WhenAnyValue(
|
|
||||||
x => x.SelectedServer,
|
|
||||||
y => y != null && !y.Text.IsNullOrEmpty())
|
|
||||||
.Subscribe(async c => await ServerSelectedChanged(c));
|
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(
|
||||||
x => x.ServerFilter,
|
x => x.ServerFilter,
|
||||||
y => y != null && _serverFilter != y)
|
y => y != null && _serverFilter != y)
|
||||||
@@ -251,11 +239,21 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
.ObserveOn(RxApp.MainThreadScheduler)
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
.Subscribe(async _ => await RefreshServersBiz());
|
.Subscribe(async _ => await RefreshServersBiz());
|
||||||
|
|
||||||
|
AppEvents.SubscriptionsRefreshRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshSubscriptions());
|
||||||
|
|
||||||
AppEvents.DispatcherStatisticsRequested
|
AppEvents.DispatcherStatisticsRequested
|
||||||
.AsObservable()
|
.AsObservable()
|
||||||
.ObserveOn(RxApp.MainThreadScheduler)
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
.Subscribe(async result => await UpdateStatistics(result));
|
.Subscribe(async result => await UpdateStatistics(result));
|
||||||
|
|
||||||
|
AppEvents.SetDefaultServerRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async indexId => await SetDefaultServer(indexId));
|
||||||
|
|
||||||
#endregion AppEvents
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
@@ -266,10 +264,9 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
SelectedProfile = new();
|
SelectedProfile = new();
|
||||||
SelectedSub = new();
|
SelectedSub = new();
|
||||||
SelectedMoveToGroup = new();
|
SelectedMoveToGroup = new();
|
||||||
SelectedServer = new();
|
|
||||||
|
|
||||||
await RefreshSubscriptions();
|
await RefreshSubscriptions();
|
||||||
await RefreshServers();
|
//await RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Init
|
#endregion Init
|
||||||
@@ -278,7 +275,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private void Reload()
|
private void Reload()
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
AppEvents.ReloadRequested.Publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetSpeedTestResult(SpeedTestResult result)
|
public async Task SetSpeedTestResult(SpeedTestResult result)
|
||||||
@@ -305,7 +302,6 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
item.SpeedVal = result.Speed ?? string.Empty;
|
item.SpeedVal = result.Speed ?? string.Empty;
|
||||||
}
|
}
|
||||||
//_profileItems.Replace(item, JsonUtils.DeepCopy(item));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStatistics(ServerSpeedItem update)
|
public async Task UpdateStatistics(ServerSpeedItem update)
|
||||||
@@ -326,17 +322,6 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
item.TodayUp = Utils.HumanFy(update.TodayUp);
|
item.TodayUp = Utils.HumanFy(update.TodayUp);
|
||||||
item.TotalDown = Utils.HumanFy(update.TotalDown);
|
item.TotalDown = Utils.HumanFy(update.TotalDown);
|
||||||
item.TotalUp = Utils.HumanFy(update.TotalUp);
|
item.TotalUp = Utils.HumanFy(update.TotalUp);
|
||||||
|
|
||||||
//if (SelectedProfile?.IndexId == item.IndexId)
|
|
||||||
//{
|
|
||||||
// var temp = JsonUtils.DeepCopy(item);
|
|
||||||
// _profileItems.Replace(item, temp);
|
|
||||||
// SelectedProfile = temp;
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// _profileItems.Replace(item, JsonUtils.DeepCopy(item));
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -376,7 +361,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
|
|
||||||
public async Task RefreshServers()
|
public async Task RefreshServers()
|
||||||
{
|
{
|
||||||
AppEvents.ProfilesRefreshRequested.OnNext(Unit.Default);
|
AppEvents.ProfilesRefreshRequested.Publish();
|
||||||
|
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
@@ -404,7 +389,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
await _updateView?.Invoke(EViewAction.DispatcherRefreshServersBiz, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshSubscriptions()
|
private async Task RefreshSubscriptions()
|
||||||
{
|
{
|
||||||
SubItems.Clear();
|
SubItems.Clear();
|
||||||
|
|
||||||
@@ -589,7 +574,7 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
await SetDefaultServer(SelectedProfile.IndexId);
|
await SetDefaultServer(SelectedProfile.IndexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetDefaultServer(string? indexId)
|
private async Task SetDefaultServer(string? indexId)
|
||||||
{
|
{
|
||||||
if (indexId.IsNullOrEmpty())
|
if (indexId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
@@ -613,19 +598,6 @@ public class ProfilesViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ServerSelectedChanged(bool c)
|
|
||||||
{
|
|
||||||
if (!c)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SelectedServer == null || SelectedServer.ID.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await SetDefaultServer(SelectedServer.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ShareServerAsync()
|
public async Task ShareServerAsync()
|
||||||
{
|
{
|
||||||
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
var item = await AppManager.Instance.GetProfileItem(SelectedProfile.IndexId);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
|
|||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public RoutingItem SelectedRouting { get; set; }
|
public RoutingItem SelectedRouting { get; set; }
|
||||||
|
|
||||||
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
|
public IObservableCollection<RulesItemModel> RulesItems { get; } = new ObservableCollectionExtended<RulesItemModel>();
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ using System.Text;
|
|||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace ServiceLib.ViewModels;
|
namespace ServiceLib.ViewModels;
|
||||||
|
|
||||||
public class StatusBarViewModel : MyReactiveObject
|
public class StatusBarViewModel : MyReactiveObject
|
||||||
{
|
{
|
||||||
|
private static readonly Lazy<StatusBarViewModel> _instance = new(() => new(null));
|
||||||
|
public static StatusBarViewModel Instance => _instance.Value;
|
||||||
|
|
||||||
#region ObservableCollection
|
#region ObservableCollection
|
||||||
|
|
||||||
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
|
public IObservableCollection<RoutingItem> RoutingItems { get; } = new ObservableCollectionExtended<RoutingItem>();
|
||||||
@@ -146,17 +148,17 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () =>
|
NotifyLeftClickCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.ShowHideWindow(null);
|
AppEvents.ShowHideWindowRequested.Publish(null);
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
});
|
});
|
||||||
ShowWindowCmd = ReactiveCommand.CreateFromTask(async () =>
|
ShowWindowCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.ShowHideWindow(true);
|
AppEvents.ShowHideWindowRequested.Publish(true);
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
});
|
});
|
||||||
HideWindowCmd = ReactiveCommand.CreateFromTask(async () =>
|
HideWindowCmd = ReactiveCommand.CreateFromTask(async () =>
|
||||||
{
|
{
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.ShowHideWindow(false);
|
AppEvents.ShowHideWindowRequested.Publish(false);
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,6 +211,26 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
.ObserveOn(RxApp.MainThreadScheduler)
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
.Subscribe(async result => await UpdateStatistics(result));
|
.Subscribe(async result => await UpdateStatistics(result));
|
||||||
|
|
||||||
|
AppEvents.RoutingsMenuRefreshRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await RefreshRoutingsMenu());
|
||||||
|
|
||||||
|
AppEvents.TestServerRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await TestServerAvailability());
|
||||||
|
|
||||||
|
AppEvents.InboundDisplayRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async _ => await InboundDisplayStatus());
|
||||||
|
|
||||||
|
AppEvents.SysProxyChangeRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(async result => await SetListenerType(result));
|
||||||
|
|
||||||
#endregion AppEvents
|
#endregion AppEvents
|
||||||
|
|
||||||
_ = Init();
|
_ = Init();
|
||||||
@@ -216,6 +238,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task Init()
|
private async Task Init()
|
||||||
{
|
{
|
||||||
|
await ConfigHandler.InitBuiltinRouting(_config);
|
||||||
await RefreshRoutingsMenu();
|
await RefreshRoutingsMenu();
|
||||||
await InboundDisplayStatus();
|
await InboundDisplayStatus();
|
||||||
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true);
|
await ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true);
|
||||||
@@ -252,23 +275,20 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
private async Task AddServerViaClipboard()
|
private async Task AddServerViaClipboard()
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
AppEvents.AddServerViaClipboardRequested.Publish();
|
||||||
if (service != null)
|
await Task.Delay(1000);
|
||||||
await service.AddServerViaClipboardAsync(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddServerViaScan()
|
private async Task AddServerViaScan()
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
AppEvents.AddServerViaScanRequested.Publish();
|
||||||
if (service != null)
|
await Task.Delay(1000);
|
||||||
await service.AddServerViaScanAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateSubscriptionProcess(bool blProxy)
|
private async Task UpdateSubscriptionProcess(bool blProxy)
|
||||||
{
|
{
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
AppEvents.SubscriptionsUpdateRequested.Publish(blProxy);
|
||||||
if (service != null)
|
await Task.Delay(1000);
|
||||||
await service.UpdateSubscriptionProcess("", blProxy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshServersBiz()
|
private async Task RefreshServersBiz()
|
||||||
@@ -329,7 +349,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Locator.Current.GetService<ProfilesViewModel>()?.SetDefaultServer(SelectedServer.ID);
|
AppEvents.SetDefaultServerRequested.Publish(SelectedServer.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TestServerAvailability()
|
public async Task TestServerAvailability()
|
||||||
@@ -364,7 +384,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region System proxy and Routings
|
#region System proxy and Routings
|
||||||
|
|
||||||
public async Task SetListenerType(ESysProxyType type)
|
private async Task SetListenerType(ESysProxyType type)
|
||||||
{
|
{
|
||||||
if (_config.SystemProxyItem.SysProxyType == type)
|
if (_config.SystemProxyItem.SysProxyType == type)
|
||||||
{
|
{
|
||||||
@@ -393,7 +413,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshRoutingsMenu()
|
private async Task RefreshRoutingsMenu()
|
||||||
{
|
{
|
||||||
RoutingItems.Clear();
|
RoutingItems.Clear();
|
||||||
|
|
||||||
@@ -430,7 +450,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
|
if (await ConfigHandler.SetDefaultRouting(_config, item) == 0)
|
||||||
{
|
{
|
||||||
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
NoticeManager.Instance.SendMessageEx(ResUI.TipChangeRouting);
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
AppEvents.ReloadRequested.Publish();
|
||||||
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
_updateView?.Invoke(EViewAction.DispatcherRefreshIcon, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -463,7 +483,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
if (Utils.IsWindows())
|
if (Utils.IsWindows())
|
||||||
{
|
{
|
||||||
_config.TunModeItem.EnableTun = false;
|
_config.TunModeItem.EnableTun = false;
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.RebootAsAdmin();
|
await AppManager.Instance.RebootAsAdmin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -477,7 +497,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await ConfigHandler.SaveConfig(_config);
|
await ConfigHandler.SaveConfig(_config);
|
||||||
Locator.Current.GetService<MainWindowViewModel>()?.Reload();
|
AppEvents.ReloadRequested.Publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AllowEnableTun()
|
private bool AllowEnableTun()
|
||||||
@@ -501,7 +521,7 @@ public class StatusBarViewModel : MyReactiveObject
|
|||||||
|
|
||||||
#region UI
|
#region UI
|
||||||
|
|
||||||
public async Task InboundDisplayStatus()
|
private async Task InboundDisplayStatus()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
sb.Append($"[{EInboundProtocol.mixed}:{AppManager.Instance.GetLocalPort(EInboundProtocol.socks)}");
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
RequestedThemeVariant="Default">
|
RequestedThemeVariant="Default">
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<semi:SemiTheme />
|
<semi:SemiTheme />
|
||||||
|
<semi:AvaloniaEditSemiTheme />
|
||||||
<StyleInclude Source="Assets/GlobalStyles.axaml" />
|
<StyleInclude Source="Assets/GlobalStyles.axaml" />
|
||||||
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
|
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
|
||||||
<dialogHost:DialogHostStyles />
|
<dialogHost:DialogHostStyles />
|
||||||
@@ -19,7 +20,6 @@
|
|||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceInclude Source="Assets/GlobalResources.axaml" />
|
<ResourceInclude Source="Assets/GlobalResources.axaml" />
|
||||||
<ResourceInclude Source="Controls/AutoCompleteBox.axaml" />
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using Splat;
|
|
||||||
using v2rayN.Desktop.Common;
|
|
||||||
using v2rayN.Desktop.Views;
|
using v2rayN.Desktop.Views;
|
||||||
|
|
||||||
namespace v2rayN.Desktop;
|
namespace v2rayN.Desktop;
|
||||||
@@ -17,9 +14,7 @@ public partial class App : Application
|
|||||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||||
|
|
||||||
var ViewModel = new StatusBarViewModel(null);
|
DataContext = StatusBarViewModel.Instance;
|
||||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(StatusBarViewModel));
|
|
||||||
DataContext = ViewModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
@@ -58,16 +53,8 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
if (desktop.MainWindow != null)
|
if (desktop.MainWindow != null)
|
||||||
{
|
{
|
||||||
var clipboardData = await AvaUtils.GetClipboardData(desktop.MainWindow);
|
AppEvents.AddServerViaClipboardRequested.Publish();
|
||||||
if (clipboardData.IsNullOrEmpty())
|
await Task.Delay(1000);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var service = Locator.Current.GetService<MainWindowViewModel>();
|
|
||||||
if (service != null)
|
|
||||||
{
|
|
||||||
_ = service.AddServerViaClipboardAsync(clipboardData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Base;
|
namespace v2rayN.Desktop.Base;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ internal class AvaUtils
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await clipboard.GetTextAsync();
|
return await clipboard.TryGetTextAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -33,9 +34,7 @@ internal class AvaUtils
|
|||||||
var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard;
|
var clipboard = TopLevel.GetTopLevel(visual)?.Clipboard;
|
||||||
if (clipboard == null)
|
if (clipboard == null)
|
||||||
return;
|
return;
|
||||||
var dataObject = new DataObject();
|
await clipboard.SetTextAsync(strData);
|
||||||
dataObject.Set(DataFormats.Text, strData);
|
|
||||||
await clipboard.SetDataObjectAsync(dataObject);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
129
v2rayN/v2rayN.Desktop/Common/TextEditorKeywordHighlighter.cs
Normal file
129
v2rayN/v2rayN.Desktop/Common/TextEditorKeywordHighlighter.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.Document;
|
||||||
|
using AvaloniaEdit.Rendering;
|
||||||
|
|
||||||
|
namespace v2rayN.Desktop.Common;
|
||||||
|
|
||||||
|
public class KeywordColorizer : DocumentColorizingTransformer
|
||||||
|
{
|
||||||
|
private readonly string[] _keywords;
|
||||||
|
private readonly Dictionary<string, IBrush> _brushMap;
|
||||||
|
|
||||||
|
public KeywordColorizer(IDictionary<string, IBrush> keywordBrushMap)
|
||||||
|
{
|
||||||
|
if (keywordBrushMap == null || keywordBrushMap.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("keywordBrushMap must not be null or empty", nameof(keywordBrushMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
_brushMap = new Dictionary<string, IBrush>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var kvp in keywordBrushMap)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(kvp.Key) || kvp.Value == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_brushMap.ContainsKey(kvp.Key))
|
||||||
|
{
|
||||||
|
_brushMap[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_brushMap.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("keywordBrushMap must contain at least one non-empty key with a non-null brush", nameof(keywordBrushMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
_keywords = _brushMap.Keys.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ColorizeLine(DocumentLine line)
|
||||||
|
{
|
||||||
|
var text = CurrentContext.Document.GetText(line);
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kw in _keywords)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(kw))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchStart = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var idx = text.IndexOf(kw, searchStart, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (idx < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kwEndIndex = idx + kw.Length;
|
||||||
|
if (IsWordCharBefore(text, idx) || IsWordCharAfter(text, kwEndIndex))
|
||||||
|
{
|
||||||
|
searchStart = idx + Math.Max(1, kw.Length);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = line.Offset + idx;
|
||||||
|
var end = start + kw.Length;
|
||||||
|
|
||||||
|
if (_brushMap.TryGetValue(kw, out var brush) && brush != null)
|
||||||
|
{
|
||||||
|
ChangeLinePart(start, end, element => element.TextRunProperties.SetForegroundBrush(brush));
|
||||||
|
}
|
||||||
|
|
||||||
|
searchStart = idx + Math.Max(1, kw.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWordCharBefore(string text, int idx)
|
||||||
|
{
|
||||||
|
if (idx <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = text[idx - 1];
|
||||||
|
return char.IsLetterOrDigit(c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWordCharAfter(string text, int idx)
|
||||||
|
{
|
||||||
|
if (idx >= text.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = text[idx];
|
||||||
|
return char.IsLetterOrDigit(c) || c == '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TextEditorKeywordHighlighter
|
||||||
|
{
|
||||||
|
public static void Attach(TextEditor editor, IDictionary<string, IBrush> keywordBrushMap)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(editor);
|
||||||
|
|
||||||
|
if (keywordBrushMap == null || keywordBrushMap.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.TextArea?.TextView?.LineTransformers?.OfType<KeywordColorizer>().Any() == true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var colorizer = new KeywordColorizer(keywordBrushMap);
|
||||||
|
editor.TextArea.TextView.LineTransformers.Add(colorizer);
|
||||||
|
editor.TextArea.TextView.InvalidateVisual();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<ResourceDictionary
|
|
||||||
xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:controls="clr-namespace:v2rayN.Desktop.Controls">
|
|
||||||
<!-- Add Resources Here -->
|
|
||||||
<ControlTheme x:Key="{x:Type controls:AutoCompleteBox}" TargetType="controls:AutoCompleteBox">
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
<Setter Property="MinHeight" Value="{DynamicResource AutoCompleteBoxDefaultHeight}" />
|
|
||||||
<Setter Property="MaxDropDownHeight" Value="{DynamicResource AutoCompleteMaxDropdownHeight}" />
|
|
||||||
<Setter Property="Template">
|
|
||||||
<ControlTemplate TargetType="AutoCompleteBox">
|
|
||||||
<Panel>
|
|
||||||
<TextBox
|
|
||||||
Name="PART_TextBox"
|
|
||||||
MinHeight="{TemplateBinding MinHeight}"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
|
|
||||||
InnerLeftContent="{TemplateBinding InnerLeftContent}"
|
|
||||||
InnerRightContent="{TemplateBinding InnerRightContent}"
|
|
||||||
Watermark="{TemplateBinding Watermark}" />
|
|
||||||
<Popup
|
|
||||||
Name="PART_Popup"
|
|
||||||
MaxHeight="{TemplateBinding MaxDropDownHeight}"
|
|
||||||
IsLightDismissEnabled="True"
|
|
||||||
PlacementTarget="{TemplateBinding}">
|
|
||||||
<Border
|
|
||||||
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
|
|
||||||
Margin="{DynamicResource AutoCompleteBoxPopupMargin}"
|
|
||||||
Padding="{DynamicResource AutoCompleteBoxPopupPadding}"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Background="{DynamicResource AutoCompleteBoxPopupBackground}"
|
|
||||||
BorderBrush="{DynamicResource AutoCompleteBoxPopupBorderBrush}"
|
|
||||||
BorderThickness="{DynamicResource AutoCompleteBoxPopupBorderThickness}"
|
|
||||||
BoxShadow="{DynamicResource AutoCompleteBoxPopupBoxShadow}"
|
|
||||||
CornerRadius="{DynamicResource AutoCompleteBoxPopupCornerRadius}">
|
|
||||||
<ListBox
|
|
||||||
Name="PART_SelectingItemsControl"
|
|
||||||
Foreground="{TemplateBinding Foreground}"
|
|
||||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
|
||||||
</Border>
|
|
||||||
</Popup>
|
|
||||||
</Panel>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter>
|
|
||||||
</ControlTheme>
|
|
||||||
</ResourceDictionary>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.Interactivity;
|
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Controls;
|
|
||||||
|
|
||||||
public class AutoCompleteBox : Avalonia.Controls.AutoCompleteBox
|
|
||||||
{
|
|
||||||
static AutoCompleteBox()
|
|
||||||
{
|
|
||||||
MinimumPrefixLengthProperty.OverrideDefaultValue<AutoCompleteBox>(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AutoCompleteBox()
|
|
||||||
{
|
|
||||||
AddHandler(PointerPressedEvent, OnBoxPointerPressed, RoutingStrategies.Tunnel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBoxPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
||||||
{
|
|
||||||
if (Equals(sender, this) && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
|
||||||
{
|
|
||||||
SetCurrentValue(IsDropDownOpenProperty, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnGotFocus(e);
|
|
||||||
if (IsDropDownOpen)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SetCurrentValue(IsDropDownOpenProperty, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
SetCurrentValue(SelectedItemProperty, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
global using ServiceLib;
|
global using ServiceLib;
|
||||||
global using ServiceLib.Base;
|
global using ServiceLib.Base;
|
||||||
global using ServiceLib.Common;
|
global using ServiceLib.Common;
|
||||||
global using ServiceLib.Enums;
|
global using ServiceLib.Enums;
|
||||||
|
global using ServiceLib.Events;
|
||||||
global using ServiceLib.Handler;
|
global using ServiceLib.Handler;
|
||||||
|
global using ServiceLib.Manager;
|
||||||
global using ServiceLib.Models;
|
global using ServiceLib.Models;
|
||||||
global using ServiceLib.Resx;
|
global using ServiceLib.Resx;
|
||||||
global using ServiceLib.ViewModels;
|
global using ServiceLib.ViewModels;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using v2rayN.Desktop.Common;
|
using v2rayN.Desktop.Common;
|
||||||
|
|
||||||
namespace v2rayN.Desktop;
|
namespace v2rayN.Desktop;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ using Avalonia.Controls.Notifications;
|
|||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
|
using AvaloniaEdit;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
using Semi.Avalonia;
|
using Semi.Avalonia;
|
||||||
using ServiceLib.Manager;
|
|
||||||
|
|
||||||
namespace v2rayN.Desktop.ViewModels;
|
namespace v2rayN.Desktop.ViewModels;
|
||||||
|
|
||||||
@@ -113,7 +113,8 @@ public class ThemeSettingViewModel : MyReactiveObject
|
|||||||
x.OfType<ContextMenu>(),
|
x.OfType<ContextMenu>(),
|
||||||
x.OfType<DataGridRow>(),
|
x.OfType<DataGridRow>(),
|
||||||
x.OfType<ListBoxItem>(),
|
x.OfType<ListBoxItem>(),
|
||||||
x.OfType<HeaderedContentControl>()
|
x.OfType<HeaderedContentControl>(),
|
||||||
|
x.OfType<TextEditor>()
|
||||||
));
|
));
|
||||||
style.Add(new Setter()
|
style.Add(new Setter()
|
||||||
{
|
{
|
||||||
@@ -154,7 +155,8 @@ public class ThemeSettingViewModel : MyReactiveObject
|
|||||||
x.OfType<DataGridRow>(),
|
x.OfType<DataGridRow>(),
|
||||||
x.OfType<ListBoxItem>(),
|
x.OfType<ListBoxItem>(),
|
||||||
x.OfType<HeaderedContentControl>(),
|
x.OfType<HeaderedContentControl>(),
|
||||||
x.OfType<WindowNotificationManager>()
|
x.OfType<WindowNotificationManager>(),
|
||||||
|
x.OfType<TextEditor>()
|
||||||
));
|
));
|
||||||
style.Add(new Setter()
|
style.Add(new Setter()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -400,7 +400,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="400"
|
Width="400"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Watermark="1000:2000,3000:4000" />
|
Watermark="1000-2000,3000,4000" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Reactive.Disposables;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Avalonia.Input;
|
|||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Splat;
|
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ public partial class ClashProxiesView : ReactiveUserControl<ClashProxiesViewMode
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
|
ViewModel = new ClashProxiesViewModel(UpdateViewHandler);
|
||||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ClashProxiesViewModel));
|
|
||||||
lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped;
|
lstProxyDetails.DoubleTapped += LstProxyDetails_DoubleTapped;
|
||||||
this.KeyDown += ClashProxiesView_KeyDown;
|
this.KeyDown += ClashProxiesView_KeyDown;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
x:Class="v2rayN.Desktop.Views.DNSSettingWindow"
|
x:Class="v2rayN.Desktop.Views.DNSSettingWindow"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ctrls="clr-namespace:v2rayN.Desktop.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
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"
|
||||||
@@ -37,6 +36,7 @@
|
|||||||
<TabItem Header="{x:Static resx:ResUI.ThBasicDNSSettings}">
|
<TabItem Header="{x:Static resx:ResUI.ThBasicDNSSettings}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Visible">
|
<ScrollViewer VerticalScrollBarVisibility="Visible">
|
||||||
<Grid
|
<Grid
|
||||||
|
x:Name="gridBasicDNSSettings"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
ColumnDefinitions="Auto,Auto,*"
|
ColumnDefinitions="Auto,Auto,*"
|
||||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
|
||||||
@@ -55,13 +55,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbDomesticDNS}" />
|
Text="{x:Static resx:ResUI.TbDomesticDNS}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbDirectDNS"
|
x:Name="cmbDirectDNS"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding DirectDNS, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
@@ -69,13 +69,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbRemoteDNS}" />
|
Text="{x:Static resx:ResUI.TbRemoteDNS}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbRemoteDNS"
|
x:Name="cmbRemoteDNS"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding RemoteDNS, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
@@ -83,13 +83,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSBOutboundsResolverDNS}" />
|
Text="{x:Static resx:ResUI.TbSBOutboundsResolverDNS}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbSBResolverDNS"
|
x:Name="cmbSBResolverDNS"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding SingboxOutboundsResolveDNS, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -103,14 +103,14 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSBDoHResolverServer}" />
|
Text="{x:Static resx:ResUI.TbSBBootstrapDNS}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbSBFinalResolverDNS"
|
x:Name="cmbSBFinalResolverDNS"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding SingboxFinalResolveDNS, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -173,13 +173,6 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left" />
|
HorizontalAlignment="Left" />
|
||||||
<TextBlock
|
|
||||||
Grid.Row="8"
|
|
||||||
Grid.Column="2"
|
|
||||||
Margin="{StaticResource Margin4}"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Text="{x:Static resx:ResUI.TbSBDoHOverride}"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@@ -187,6 +180,7 @@
|
|||||||
<TabItem Header="{x:Static resx:ResUI.ThAdvancedDNSSettings}">
|
<TabItem Header="{x:Static resx:ResUI.ThAdvancedDNSSettings}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Visible">
|
<ScrollViewer VerticalScrollBarVisibility="Visible">
|
||||||
<Grid
|
<Grid
|
||||||
|
x:Name="gridAdvancedDNSSettings"
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
ColumnDefinitions="Auto,Auto,*"
|
ColumnDefinitions="Auto,Auto,*"
|
||||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
|
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
|
||||||
@@ -229,7 +223,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbApplyProxyDomainsOnly}"
|
Text="{x:Static resx:ResUI.TbFakeIPTips}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -258,13 +252,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbDirectExpectedIPs"
|
x:Name="cmbDirectExpectedIPs"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="200"
|
Width="200"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding DirectExpectedIPs, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -361,11 +355,11 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSettingsDomainDNSAddress}" />
|
Text="{x:Static resx:ResUI.TbSettingsDomainDNSAddress}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbdomainDNSAddressCompatible"
|
x:Name="cmbdomainDNSAddressCompatible"
|
||||||
Width="150"
|
Width="150"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding DomainDNSAddressCompatible, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
|
||||||
@@ -433,11 +427,11 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSettingsDomainDNSAddress}" />
|
Text="{x:Static resx:ResUI.TbSettingsDomainDNSAddress}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbdomainDNSAddress2Compatible"
|
x:Name="cmbdomainDNSAddress2Compatible"
|
||||||
Width="150"
|
Width="150"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding DomainDNSAddress2Compatible, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
@@ -40,15 +40,15 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
|||||||
this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.SingboxOutboundsResolveDNS, v => v.cmbSBResolverDNS.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SingboxOutboundsResolveDNS, v => v.cmbSBResolverDNS.Text).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.SingboxFinalResolveDNS, v => v.cmbSBFinalResolverDNS.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SingboxFinalResolveDNS, v => v.cmbSBFinalResolverDNS.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);
|
||||||
|
|
||||||
@@ -57,27 +57,25 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
|
|||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.UseSystemHostsCompatible, v => v.togUseSystemHostsCompatible.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.UseSystemHostsCompatible, v => v.togUseSystemHostsCompatible.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.DomainStrategy4FreedomCompatible, v => v.cmbdomainStrategy4FreedomCompatible.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.DomainStrategy4FreedomCompatible, v => v.cmbdomainStrategy4FreedomCompatible.SelectedItem).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.DomainDNSAddressCompatible, v => v.cmbdomainDNSAddressCompatible.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.DomainDNSAddressCompatible, v => v.cmbdomainDNSAddressCompatible.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.NormalDNSCompatible, v => v.txtnormalDNSCompatible.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.NormalDNSCompatible, v => v.txtnormalDNSCompatible.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.DomainStrategy4Freedom2Compatible, v => v.cmbdomainStrategy4OutCompatible.SelectedItem).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.DomainStrategy4Freedom2Compatible, v => v.cmbdomainStrategy4OutCompatible.SelectedItem).DisposeWith(disposables);
|
||||||
//this.Bind(ViewModel, vm => vm.DomainDNSAddress2Compatible, v => v.cmbdomainDNSAddress2Compatible.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.DomainDNSAddress2Compatible, v => v.cmbdomainDNSAddress2Compatible.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.NormalDNS2Compatible, v => v.txtnormalDNS2Compatible.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.NormalDNS2Compatible, v => v.txtnormalDNS2Compatible.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.TunDNS2Compatible, v => v.txttunDNS2Compatible.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.TunDNS2Compatible, v => v.txttunDNS2Compatible.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
this.BindCommand(ViewModel, vm => vm.ImportDefConfig4V2rayCompatibleCmd, v => v.btnImportDefConfig4V2rayCompatible).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ImportDefConfig4V2rayCompatibleCmd, v => v.btnImportDefConfig4V2rayCompatible).DisposeWith(disposables);
|
||||||
this.BindCommand(ViewModel, vm => vm.ImportDefConfig4SingboxCompatibleCmd, v => v.btnImportDefConfig4SingboxCompatible).DisposeWith(disposables);
|
this.BindCommand(ViewModel, vm => vm.ImportDefConfig4SingboxCompatibleCmd, v => v.btnImportDefConfig4SingboxCompatible).DisposeWith(disposables);
|
||||||
|
|
||||||
this.WhenAnyValue(
|
this.WhenAnyValue(x => x.ViewModel.IsSimpleDNSEnabled)
|
||||||
x => x.ViewModel.RayCustomDNSEnableCompatible,
|
.Select(b => !b)
|
||||||
x => x.ViewModel.SBCustomDNSEnableCompatible,
|
.BindTo(this.FindControl<TextBlock>("txtBasicDNSSettingsInvalid"), t => t.IsVisible);
|
||||||
(ray, sb) => ray && sb
|
this.WhenAnyValue(x => x.ViewModel.IsSimpleDNSEnabled)
|
||||||
).BindTo(this.FindControl<TextBlock>("txtBasicDNSSettingsInvalid"), t => t.IsVisible);
|
.Select(b => !b)
|
||||||
this.WhenAnyValue(
|
.BindTo(this.FindControl<TextBlock>("txtAdvancedDNSSettingsInvalid"), t => t.IsVisible);
|
||||||
x => x.ViewModel.RayCustomDNSEnableCompatible,
|
this.Bind(ViewModel, vm => vm.IsSimpleDNSEnabled, v => v.gridBasicDNSSettings.IsEnabled).DisposeWith(disposables);
|
||||||
x => x.ViewModel.SBCustomDNSEnableCompatible,
|
this.Bind(ViewModel, vm => vm.IsSimpleDNSEnabled, v => v.gridAdvancedDNSSettings.IsEnabled).DisposeWith(disposables);
|
||||||
(ray, sb) => ray && sb
|
|
||||||
).BindTo(this.FindControl<TextBlock>("txtAdvancedDNSSettingsInvalid"), t => t.IsVisible);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ using Avalonia.Threading;
|
|||||||
using DialogHostAvalonia;
|
using DialogHostAvalonia;
|
||||||
using MsBox.Avalonia.Enums;
|
using MsBox.Avalonia.Enums;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using Splat;
|
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
using v2rayN.Desktop.Common;
|
using v2rayN.Desktop.Common;
|
||||||
using v2rayN.Desktop.Manager;
|
using v2rayN.Desktop.Manager;
|
||||||
@@ -21,7 +19,7 @@ namespace v2rayN.Desktop.Views;
|
|||||||
public partial class MainWindow : WindowBase<MainWindowViewModel>
|
public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||||
{
|
{
|
||||||
private static Config _config;
|
private static Config _config;
|
||||||
private WindowNotificationManager? _manager;
|
private readonly WindowNotificationManager? _manager;
|
||||||
private CheckUpdateView? _checkUpdateView;
|
private CheckUpdateView? _checkUpdateView;
|
||||||
private BackupAndRestoreView? _backupAndRestoreView;
|
private BackupAndRestoreView? _backupAndRestoreView;
|
||||||
private bool _blCloseByUser = false;
|
private bool _blCloseByUser = false;
|
||||||
@@ -41,7 +39,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
menuClose.Click += MenuClose_Click;
|
menuClose.Click += MenuClose_Click;
|
||||||
|
|
||||||
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
ViewModel = new MainWindowViewModel(UpdateViewHandler);
|
||||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(MainWindowViewModel));
|
|
||||||
|
|
||||||
switch (_config.UiItem.MainGirdOrientation)
|
switch (_config.UiItem.MainGirdOrientation)
|
||||||
{
|
{
|
||||||
@@ -154,6 +151,12 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
.ObserveOn(RxApp.MainThreadScheduler)
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
.Subscribe(content => Shutdown(content))
|
.Subscribe(content => Shutdown(content))
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
AppEvents.ShowHideWindowRequested
|
||||||
|
.AsObservable()
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(blShow => ShowHideWindow(blShow))
|
||||||
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Utils.IsWindows())
|
if (Utils.IsWindows())
|
||||||
@@ -222,12 +225,6 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
case EViewAction.SubSettingWindow:
|
case EViewAction.SubSettingWindow:
|
||||||
return await new SubSettingWindow().ShowDialog<bool>(this);
|
return await new SubSettingWindow().ShowDialog<bool>(this);
|
||||||
|
|
||||||
case EViewAction.ShowHideWindow:
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
ShowHideWindow((bool?)obj),
|
|
||||||
DispatcherPriority.Default);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EViewAction.ScanScreenTask:
|
case EViewAction.ScanScreenTask:
|
||||||
await ScanScreenTaskAsync();
|
await ScanScreenTaskAsync();
|
||||||
break;
|
break;
|
||||||
@@ -237,11 +234,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EViewAction.AddServerViaClipboard:
|
case EViewAction.AddServerViaClipboard:
|
||||||
var clipboardData = await AvaUtils.GetClipboardData(this);
|
await AddServerViaClipboardAsync();
|
||||||
if (clipboardData.IsNotEmpty() && ViewModel != null)
|
|
||||||
{
|
|
||||||
await ViewModel.AddServerViaClipboardAsync(clipboardData);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +253,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
case EGlobalHotkey.SystemProxySet:
|
case EGlobalHotkey.SystemProxySet:
|
||||||
case EGlobalHotkey.SystemProxyUnchanged:
|
case EGlobalHotkey.SystemProxyUnchanged:
|
||||||
case EGlobalHotkey.SystemProxyPac:
|
case EGlobalHotkey.SystemProxyPac:
|
||||||
Locator.Current.GetService<StatusBarViewModel>()?.SetListenerType((ESysProxyType)((int)e - 1));
|
AppEvents.SysProxyChangeRequested.Publish((ESysProxyType)((int)e - 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,11 +289,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.V:
|
case Key.V:
|
||||||
var clipboardData = await AvaUtils.GetClipboardData(this);
|
await AddServerViaClipboardAsync();
|
||||||
if (clipboardData.IsNotEmpty() && ViewModel != null)
|
|
||||||
{
|
|
||||||
await ViewModel.AddServerViaClipboardAsync(clipboardData);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Key.S:
|
case Key.S:
|
||||||
@@ -327,6 +316,15 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
|||||||
ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
|
ProcUtils.ProcessStart(Utils.GetBinPath("EnableLoopback.exe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddServerViaClipboardAsync()
|
||||||
|
{
|
||||||
|
var clipboardData = await AvaUtils.GetClipboardData(this);
|
||||||
|
if (clipboardData.IsNotEmpty() && ViewModel != null)
|
||||||
|
{
|
||||||
|
await ViewModel.AddServerViaClipboardAsync(clipboardData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ScanScreenTaskAsync()
|
public async Task ScanScreenTaskAsync()
|
||||||
{
|
{
|
||||||
//ShowHideWindow(false);
|
//ShowHideWindow(false);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
x:Class="v2rayN.Desktop.Views.MsgView"
|
x:Class="v2rayN.Desktop.Views.MsgView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
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"
|
||||||
@@ -70,35 +71,36 @@
|
|||||||
Theme="{DynamicResource SimpleToggleSwitch}" />
|
Theme="{DynamicResource SimpleToggleSwitch}" />
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
|
||||||
<ScrollViewer x:Name="msgScrollViewer" VerticalScrollBarVisibility="Auto">
|
<avaloniaEdit:TextEditor
|
||||||
<SelectableTextBlock
|
Name="txtMsg"
|
||||||
Name="txtMsg"
|
Margin="{StaticResource Margin8}"
|
||||||
Margin="{StaticResource Margin8}"
|
IsReadOnly="True"
|
||||||
VerticalAlignment="Stretch"
|
VerticalScrollBarVisibility="Auto"
|
||||||
Classes="TextArea"
|
WordWrap="True">
|
||||||
TextAlignment="Left"
|
<avaloniaEdit:TextEditor.Options>
|
||||||
TextWrapping="Wrap">
|
<avaloniaEdit:TextEditorOptions AllowScrollBelowDocument="False"/>
|
||||||
<SelectableTextBlock.ContextMenu>
|
</avaloniaEdit:TextEditor.Options>
|
||||||
<ContextMenu>
|
<avaloniaEdit:TextEditor.ContextFlyout>
|
||||||
<MenuItem
|
<MenuFlyout>
|
||||||
x:Name="menuMsgViewSelectAll"
|
<MenuItem
|
||||||
Click="menuMsgViewSelectAll_Click"
|
x:Name="menuMsgViewSelectAll"
|
||||||
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
|
Click="menuMsgViewSelectAll_Click"
|
||||||
<MenuItem
|
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
|
||||||
x:Name="menuMsgViewCopy"
|
<MenuItem
|
||||||
Click="menuMsgViewCopy_Click"
|
x:Name="menuMsgViewCopy"
|
||||||
Header="{x:Static resx:ResUI.menuMsgViewCopy}" />
|
Click="menuMsgViewCopy_Click"
|
||||||
<MenuItem
|
Header="{x:Static resx:ResUI.menuMsgViewCopy}" />
|
||||||
x:Name="menuMsgViewCopyAll"
|
<MenuItem
|
||||||
Click="menuMsgViewCopyAll_Click"
|
x:Name="menuMsgViewCopyAll"
|
||||||
Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" />
|
Click="menuMsgViewCopyAll_Click"
|
||||||
<MenuItem
|
Header="{x:Static resx:ResUI.menuMsgViewCopyAll}" />
|
||||||
x:Name="menuMsgViewClear"
|
<MenuItem
|
||||||
Click="menuMsgViewClear_Click"
|
x:Name="menuMsgViewClear"
|
||||||
Header="{x:Static resx:ResUI.menuMsgViewClear}" />
|
Click="menuMsgViewClear_Click"
|
||||||
</ContextMenu>
|
Header="{x:Static resx:ResUI.menuMsgViewClear}" />
|
||||||
</SelectableTextBlock.ContextMenu>
|
</MenuFlyout>
|
||||||
</SelectableTextBlock>
|
</avaloniaEdit:TextEditor.ContextFlyout>
|
||||||
</ScrollViewer>
|
</avaloniaEdit:TextEditor>
|
||||||
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
@@ -10,13 +10,12 @@ namespace v2rayN.Desktop.Views;
|
|||||||
|
|
||||||
public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
||||||
{
|
{
|
||||||
private readonly ScrollViewer _scrollViewer;
|
//private const int KeepLines = 30;
|
||||||
|
|
||||||
public MsgView()
|
public MsgView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_scrollViewer = this.FindControl<ScrollViewer>("msgScrollViewer");
|
txtMsg.TextArea.TextView.Options.EnableHyperlinks = false;
|
||||||
|
|
||||||
ViewModel = new MsgViewModel(UpdateViewHandler);
|
ViewModel = new MsgViewModel(UpdateViewHandler);
|
||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
@@ -24,6 +23,11 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
|||||||
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TextEditorKeywordHighlighter.Attach(txtMsg, Global.LogLevelColors.ToDictionary(
|
||||||
|
kv => kv.Key,
|
||||||
|
kv => (IBrush)new SolidColorBrush(Color.Parse(kv.Value))
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
@@ -34,9 +38,8 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
|||||||
if (obj is null)
|
if (obj is null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() => ShowMsg(obj),
|
||||||
ShowMsg(obj),
|
DispatcherPriority.ApplicationIdle);
|
||||||
DispatcherPriority.ApplicationIdle);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return await Task.FromResult(true);
|
return await Task.FromResult(true);
|
||||||
@@ -44,23 +47,37 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
|||||||
|
|
||||||
private void ShowMsg(object msg)
|
private void ShowMsg(object msg)
|
||||||
{
|
{
|
||||||
txtMsg.Text = msg.ToString();
|
//var lineCount = txtMsg.LineCount;
|
||||||
|
//if (lineCount > ViewModel?.NumMaxMsg)
|
||||||
|
//{
|
||||||
|
// var cutLine = txtMsg.Document.GetLineByNumber(lineCount - KeepLines);
|
||||||
|
// txtMsg.Document.Remove(0, cutLine.Offset);
|
||||||
|
//}
|
||||||
|
if (txtMsg.LineCount > ViewModel?.NumMaxMsg)
|
||||||
|
{
|
||||||
|
ClearMsg();
|
||||||
|
}
|
||||||
|
|
||||||
|
txtMsg.AppendText(msg.ToString());
|
||||||
if (togScrollToEnd.IsChecked ?? true)
|
if (togScrollToEnd.IsChecked ?? true)
|
||||||
{
|
{
|
||||||
_scrollViewer?.ScrollToEnd();
|
txtMsg.ScrollToEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearMsg()
|
public void ClearMsg()
|
||||||
{
|
{
|
||||||
ViewModel?.ClearMsg();
|
txtMsg.Clear();
|
||||||
txtMsg.Text = "";
|
txtMsg.AppendText("----- Message cleared -----\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e)
|
private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
txtMsg.Focus();
|
Dispatcher.UIThread.Post(() =>
|
||||||
txtMsg.SelectAll();
|
{
|
||||||
|
txtMsg.TextArea.Focus();
|
||||||
|
txtMsg.SelectAll();
|
||||||
|
}, DispatcherPriority.Render);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e)
|
private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
x:Class="v2rayN.Desktop.Views.OptionSettingWindow"
|
x:Class="v2rayN.Desktop.Views.OptionSettingWindow"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ctrls="clr-namespace:v2rayN.Desktop.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
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"
|
||||||
@@ -502,12 +501,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamily}" />
|
Text="{x:Static resx:ResUI.TbSettingsCurrentFontFamily}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbcurrentFontFamily"
|
x:Name="cmbcurrentFontFamily"
|
||||||
Grid.Row="15"
|
Grid.Row="15"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="15"
|
Grid.Row="15"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -548,12 +548,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSettingsSpeedTestUrl}" />
|
Text="{x:Static resx:ResUI.TbSettingsSpeedTestUrl}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
Name="cmbSpeedTestUrl"
|
Name="cmbSpeedTestUrl"
|
||||||
Grid.Row="18"
|
Grid.Row="18"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="19"
|
Grid.Row="19"
|
||||||
@@ -561,12 +562,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSettingsSpeedPingTestUrl}" />
|
Text="{x:Static resx:ResUI.TbSettingsSpeedPingTestUrl}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbSpeedPingTestUrl"
|
x:Name="cmbSpeedPingTestUrl"
|
||||||
Grid.Row="19"
|
Grid.Row="19"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="20"
|
Grid.Row="20"
|
||||||
@@ -587,12 +589,13 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
|
Text="{x:Static resx:ResUI.TbSettingsSubConvert}" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
x:Name="cmbSubConvertUrl"
|
x:Name="cmbSubConvertUrl"
|
||||||
Grid.Row="21"
|
Grid.Row="21"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="22"
|
Grid.Row="22"
|
||||||
@@ -618,7 +621,8 @@
|
|||||||
Grid.Row="23"
|
Grid.Row="23"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="23"
|
Grid.Row="23"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -638,7 +642,8 @@
|
|||||||
Grid.Row="24"
|
Grid.Row="24"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="24"
|
Grid.Row="24"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -658,7 +663,8 @@
|
|||||||
Grid.Row="25"
|
Grid.Row="25"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}" />
|
Margin="{StaticResource Margin4}"
|
||||||
|
IsEditable="True" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="25"
|
Grid.Row="25"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Reactive.Disposables;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|||||||
128
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml
Normal file
128
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="v2rayN.Desktop.Views.ProfilesSelectWindow"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||||
|
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||||
|
Title="{x:Static resx:ResUI.TbSelectProfile}"
|
||||||
|
Width="800"
|
||||||
|
Height="450"
|
||||||
|
x:DataType="vms:ProfilesSelectViewModel"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<DockPanel Margin="8">
|
||||||
|
<!-- Bottom buttons -->
|
||||||
|
<StackPanel
|
||||||
|
Margin="4"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
DockPanel.Dock="Bottom"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnSave"
|
||||||
|
Width="100"
|
||||||
|
Click="BtnSave_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbConfirm}" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnCancel"
|
||||||
|
Width="100"
|
||||||
|
Margin="8,0"
|
||||||
|
Content="{x:Static resx:ResUI.TbCancel}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<DockPanel>
|
||||||
|
<!-- Top tools -->
|
||||||
|
<WrapPanel Margin="4" DockPanel.Dock="Top">
|
||||||
|
<ListBox
|
||||||
|
x:Name="lstGroup"
|
||||||
|
Margin="{StaticResource MarginLr4}"
|
||||||
|
DisplayMemberBinding="{Binding Remarks}"
|
||||||
|
ItemsSource="{Binding SubItems}"
|
||||||
|
Theme="{DynamicResource ButtonRadioGroupListBox}">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="btnAutofitColumnWidth"
|
||||||
|
Width="32"
|
||||||
|
Height="32"
|
||||||
|
Margin="8,0"
|
||||||
|
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
|
||||||
|
<Button.Content>
|
||||||
|
<PathIcon Data="{StaticResource building_fit}" />
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtServerFilter"
|
||||||
|
Width="200"
|
||||||
|
Margin="8,0"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Text="{Binding ServerFilter, Mode=TwoWay}"
|
||||||
|
Watermark="{x:Static resx:ResUI.MsgServerTitle}" />
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<!-- Profiles grid -->
|
||||||
|
<DataGrid
|
||||||
|
x:Name="lstProfiles"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
BorderThickness="1"
|
||||||
|
CanUserReorderColumns="True"
|
||||||
|
CanUserResizeColumns="True"
|
||||||
|
GridLinesVisibility="All"
|
||||||
|
HeadersVisibility="All"
|
||||||
|
IsReadOnly="True"
|
||||||
|
ItemsSource="{Binding ProfileItems}"
|
||||||
|
SelectionMode="Single">
|
||||||
|
<DataGrid.KeyBindings>
|
||||||
|
<KeyBinding Command="{Binding SelectFinish}" Gesture="Enter" />
|
||||||
|
</DataGrid.KeyBindings>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="80"
|
||||||
|
Binding="{Binding ConfigType}"
|
||||||
|
Header="{x:Static resx:ResUI.LvServiceType}"
|
||||||
|
Tag="ConfigType" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="120"
|
||||||
|
Binding="{Binding Remarks}"
|
||||||
|
Header="{x:Static resx:ResUI.LvRemarks}"
|
||||||
|
Tag="Remarks" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="120"
|
||||||
|
Binding="{Binding Address}"
|
||||||
|
Header="{x:Static resx:ResUI.LvAddress}"
|
||||||
|
Tag="Address" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="60"
|
||||||
|
Binding="{Binding Port}"
|
||||||
|
Header="{x:Static resx:ResUI.LvPort}"
|
||||||
|
Tag="Port" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding Network}"
|
||||||
|
Header="{x:Static resx:ResUI.LvTransportProtocol}"
|
||||||
|
Tag="Network" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding StreamSecurity}"
|
||||||
|
Header="{x:Static resx:ResUI.LvTLS}"
|
||||||
|
Tag="StreamSecurity" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="100"
|
||||||
|
Binding="{Binding SubRemarks}"
|
||||||
|
Header="{x:Static resx:ResUI.LvSubscription}"
|
||||||
|
Tag="SubRemarks" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</Window>
|
||||||
194
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs
Normal file
194
v2rayN/v2rayN.Desktop/Views/ProfilesSelectWindow.axaml.cs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
using System.Reactive.Disposables;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using ReactiveUI;
|
||||||
|
using v2rayN.Desktop.Base;
|
||||||
|
|
||||||
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
|
public partial class ProfilesSelectWindow : WindowBase<ProfilesSelectViewModel>
|
||||||
|
{
|
||||||
|
private static Config _config;
|
||||||
|
|
||||||
|
public Task<ProfileItem?> ProfileItem => GetProfileItem();
|
||||||
|
public Task<List<ProfileItem>?> ProfileItems => GetProfileItems();
|
||||||
|
private bool _allowMultiSelect = false;
|
||||||
|
|
||||||
|
public ProfilesSelectWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_config = AppManager.Instance.Config;
|
||||||
|
|
||||||
|
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
|
||||||
|
txtServerFilter.KeyDown += TxtServerFilter_KeyDown;
|
||||||
|
lstProfiles.KeyDown += LstProfiles_KeyDown;
|
||||||
|
lstProfiles.SelectionChanged += LstProfiles_SelectionChanged;
|
||||||
|
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
|
||||||
|
lstProfiles.Sorting += LstProfiles_Sorting;
|
||||||
|
lstProfiles.DoubleTapped += LstProfiles_DoubleTapped;
|
||||||
|
|
||||||
|
ViewModel = new ProfilesSelectViewModel(UpdateViewHandler);
|
||||||
|
DataContext = ViewModel;
|
||||||
|
|
||||||
|
this.WhenActivated(disposables =>
|
||||||
|
{
|
||||||
|
this.OneWayBind(ViewModel, vm => vm.ProfileItems, v => v.lstProfiles.ItemsSource).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.SelectedProfile, v => v.lstProfiles.SelectedItem).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables);
|
||||||
|
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCancel.Click += (s, e) => Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AllowMultiSelect(bool allow)
|
||||||
|
{
|
||||||
|
_allowMultiSelect = allow;
|
||||||
|
if (allow)
|
||||||
|
{
|
||||||
|
lstProfiles.SelectionMode = DataGridSelectionMode.Extended;
|
||||||
|
lstProfiles.SelectedItems.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lstProfiles.SelectionMode = DataGridSelectionMode.Single;
|
||||||
|
if (lstProfiles.SelectedItems.Count > 0)
|
||||||
|
{
|
||||||
|
var first = lstProfiles.SelectedItems[0];
|
||||||
|
lstProfiles.SelectedItems.Clear();
|
||||||
|
lstProfiles.SelectedItem = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose ConfigType filter controls to callers
|
||||||
|
public void SetConfigTypeFilter(IEnumerable<EConfigType> types, bool exclude = false)
|
||||||
|
=> ViewModel?.SetConfigTypeFilter(types, exclude);
|
||||||
|
|
||||||
|
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case EViewAction.CloseWindow:
|
||||||
|
Close(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel != null)
|
||||||
|
{
|
||||||
|
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e)
|
||||||
|
{
|
||||||
|
e.Row.Header = $" {e.Row.Index + 1}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_DoubleTapped(object? sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
// 忽略表头区域的双击
|
||||||
|
if (e.Source is Control src)
|
||||||
|
{
|
||||||
|
if (src.FindAncestorOfType<DataGridColumnHeader>() != null)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅当在数据行或其子元素上双击时才触发选择
|
||||||
|
if (src.FindAncestorOfType<DataGridRow>() != null)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
||||||
|
{
|
||||||
|
// 自定义排序,防止默认行为导致误触发
|
||||||
|
e.Handled = true;
|
||||||
|
if (ViewModel != null && e.Column?.Tag?.ToString() != null)
|
||||||
|
{
|
||||||
|
ViewModel.SortServer(e.Column.Tag.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LstProfiles_KeyDown(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.KeyModifiers is KeyModifiers.Control or KeyModifiers.Meta)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.A)
|
||||||
|
{
|
||||||
|
if (_allowMultiSelect)
|
||||||
|
{
|
||||||
|
lstProfiles.SelectAll();
|
||||||
|
}
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AutofitColumnWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutofitColumnWidth()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var col in lstProfiles.Columns)
|
||||||
|
{
|
||||||
|
col.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TxtServerFilter_KeyDown(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key is Key.Enter or Key.Return)
|
||||||
|
{
|
||||||
|
ViewModel?.RefreshServers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProfileItem?> GetProfileItem()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItem();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ProfileItem>?> GetProfileItems()
|
||||||
|
{
|
||||||
|
var item = await ViewModel?.GetProfileItems();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Trigger selection finalize when Confirm is clicked
|
||||||
|
ViewModel?.SelectFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,6 @@ using Avalonia.Threading;
|
|||||||
using DialogHostAvalonia;
|
using DialogHostAvalonia;
|
||||||
using MsBox.Avalonia.Enums;
|
using MsBox.Avalonia.Enums;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using Splat;
|
|
||||||
using v2rayN.Desktop.Common;
|
using v2rayN.Desktop.Common;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
@@ -49,7 +47,6 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
ViewModel = new ProfilesViewModel(UpdateViewHandler);
|
ViewModel = new ProfilesViewModel(UpdateViewHandler);
|
||||||
Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(ProfilesViewModel));
|
|
||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
@@ -112,7 +109,6 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
|
|||||||
});
|
});
|
||||||
|
|
||||||
RestoreUI();
|
RestoreUI();
|
||||||
ViewModel?.RefreshServers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
|
||||||
|
|||||||
@@ -4,19 +4,25 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
d:DesignHeight="480"
|
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
||||||
d:DesignWidth="400"
|
d:DesignHeight="600"
|
||||||
|
d:DesignWidth="600"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<sys:Double x:Key="QrcodeWidth">400</sys:Double>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid Margin="32" RowDefinitions="Auto,Auto">
|
<Grid Margin="32" RowDefinitions="Auto,Auto">
|
||||||
<Image
|
<Image
|
||||||
Name="imgQrcode"
|
Name="imgQrcode"
|
||||||
Width="300"
|
Width="{StaticResource QrcodeWidth}"
|
||||||
Height="300" />
|
Height="{StaticResource QrcodeWidth}" />
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtContent"
|
x:Name="txtContent"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Width="300"
|
Width="{StaticResource QrcodeWidth}"
|
||||||
MaxHeight="100"
|
MaxHeight="100"
|
||||||
Margin="{StaticResource MarginTb8}"
|
Margin="{StaticResource MarginTb8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
|||||||
@@ -23,8 +23,16 @@ public partial class QrcodeView : UserControl
|
|||||||
|
|
||||||
private Bitmap? GetQRCode(string? url)
|
private Bitmap? GetQRCode(string? url)
|
||||||
{
|
{
|
||||||
var bytes = QRCodeUtils.GenQRCode(url);
|
try
|
||||||
return ByteToBitmap(bytes);
|
{
|
||||||
|
var bytes = QRCodeUtils.GenQRCode(url);
|
||||||
|
return ByteToBitmap(bytes);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog("GetQRCode", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap? ByteToBitmap(byte[]? bytes)
|
private Bitmap? ByteToBitmap(byte[]? bytes)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
x:Class="v2rayN.Desktop.Views.RoutingRuleDetailsWindow"
|
x:Class="v2rayN.Desktop.Views.RoutingRuleDetailsWindow"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ctrls="clr-namespace:v2rayN.Desktop.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
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"
|
||||||
@@ -47,20 +46,27 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="outboundTag" />
|
Text="outboundTag" />
|
||||||
<ctrls:AutoCompleteBox
|
<ComboBox
|
||||||
Name="cmbOutboundTag"
|
Name="cmbOutboundTag"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Width="300"
|
Width="300"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
Text="{Binding SelectedSource.OutboundTag, Mode=TwoWay}" />
|
IsEditable="True" />
|
||||||
<TextBlock
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnSelectProfile"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
Click="BtnSelectProfile_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}" />
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
|
|||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
|
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Remarks, v => v.txtRemarks.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.OutboundTag, v => v.cmbOutboundTag.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.SelectedSource.Port, v => v.txtPort.Text).DisposeWith(disposables);
|
||||||
@@ -93,4 +94,19 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
|
|||||||
{
|
{
|
||||||
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
|
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectProfile_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||||
|
if (result == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
cmbOutboundTag.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,7 @@
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
x:Name="lstRules"
|
x:Name="lstRules"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
|
Background="Transparent"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CanUserResizeColumns="True"
|
CanUserResizeColumns="True"
|
||||||
GridLinesVisibility="All"
|
GridLinesVisibility="All"
|
||||||
|
|||||||
@@ -92,6 +92,7 @@
|
|||||||
<DataGrid
|
<DataGrid
|
||||||
x:Name="lstRoutings"
|
x:Name="lstRoutings"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
|
Background="Transparent"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CanUserResizeColumns="True"
|
CanUserResizeColumns="True"
|
||||||
GridLinesVisibility="All"
|
GridLinesVisibility="All"
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ using Avalonia.ReactiveUI;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DialogHostAvalonia;
|
using DialogHostAvalonia;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
|
||||||
using Splat;
|
|
||||||
using v2rayN.Desktop.Common;
|
using v2rayN.Desktop.Common;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
@@ -21,9 +19,8 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_config = AppManager.Instance.Config;
|
_config = AppManager.Instance.Config;
|
||||||
//ViewModel = new StatusBarViewModel(UpdateViewHandler);
|
|
||||||
//Locator.CurrentMutable.RegisterLazySingleton(() => ViewModel, typeof(StatusBarViewModel));
|
ViewModel = StatusBarViewModel.Instance;
|
||||||
ViewModel = Locator.Current.GetService<StatusBarViewModel>();
|
|
||||||
ViewModel?.InitUpdateView(UpdateViewHandler);
|
ViewModel?.InitUpdateView(UpdateViewHandler);
|
||||||
|
|
||||||
txtRunningServerDisplay.Tapped += TxtRunningServerDisplay_Tapped;
|
txtRunningServerDisplay.Tapped += TxtRunningServerDisplay_Tapped;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
Width="{StaticResource IconButtonWidth}"
|
Width="{StaticResource IconButtonWidth}"
|
||||||
Height="{StaticResource IconButtonHeight}"
|
Height="{StaticResource IconButtonHeight}"
|
||||||
Margin="{StaticResource MarginLr8}"
|
Margin="{StaticResource MarginLr8}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
Theme="{DynamicResource BorderlessButton}">
|
Theme="{DynamicResource BorderlessButton}">
|
||||||
<Button.Content>
|
<Button.Content>
|
||||||
<PathIcon Data="{StaticResource building_more}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
|
<PathIcon Data="{StaticResource building_more}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
|
||||||
@@ -204,6 +205,12 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="9"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Click="BtnSelectPrevProfile_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="10"
|
Grid.Row="10"
|
||||||
@@ -218,6 +225,12 @@
|
|||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
|
||||||
|
<Button
|
||||||
|
Grid.Row="10"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="{StaticResource Margin4}"
|
||||||
|
Click="BtnSelectNextProfile_Click"
|
||||||
|
Content="{x:Static resx:ResUI.TbSelectProfile}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="11"
|
Grid.Row="11"
|
||||||
|
|||||||
@@ -59,4 +59,34 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
|
|||||||
{
|
{
|
||||||
txtRemarks.Focus();
|
txtRemarks.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||||
|
if (result == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
txtPrevProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var selectWindow = new ProfilesSelectWindow();
|
||||||
|
selectWindow.SetConfigTypeFilter(new[] { EConfigType.Custom }, exclude: true);
|
||||||
|
var result = await selectWindow.ShowDialog<bool?>(this);
|
||||||
|
if (result == true)
|
||||||
|
{
|
||||||
|
var profile = await selectWindow.ProfileItem;
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
txtNextProfile.Text = profile.Remarks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
using DialogHostAvalonia;
|
using DialogHostAvalonia;
|
||||||
using ServiceLib.Manager;
|
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia.AvaloniaEdit" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid">
|
<PackageReference Include="Avalonia.Controls.DataGrid">
|
||||||
<TreatAsUsed>true</TreatAsUsed>
|
<TreatAsUsed>true</TreatAsUsed>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
<PackageReference Include="Avalonia.ReactiveUI" />
|
<PackageReference Include="Avalonia.ReactiveUI" />
|
||||||
<PackageReference Include="MessageBox.Avalonia" />
|
<PackageReference Include="MessageBox.Avalonia" />
|
||||||
<PackageReference Include="Semi.Avalonia" />
|
<PackageReference Include="Semi.Avalonia" />
|
||||||
|
<PackageReference Include="Semi.Avalonia.AvaloniaEdit" />
|
||||||
<PackageReference Include="Semi.Avalonia.DataGrid">
|
<PackageReference Include="Semi.Avalonia.DataGrid">
|
||||||
<TreatAsUsed>true</TreatAsUsed>
|
<TreatAsUsed>true</TreatAsUsed>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
24
v2rayN/v2rayN.slnx
Normal file
24
v2rayN/v2rayN.slnx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/GitHub Action/">
|
||||||
|
<File Path="../.github/workflows/build-all.yml" />
|
||||||
|
<File Path="../.github/workflows/build-linux.yml" />
|
||||||
|
<File Path="../.github/workflows/build-osx.yml" />
|
||||||
|
<File Path="../.github/workflows/build-windows-desktop.yml" />
|
||||||
|
<File Path="../.github/workflows/build-windows.yml" />
|
||||||
|
<File Path="../.github/workflows/winget-publish.yml" />
|
||||||
|
<File Path="../package-appimage.sh" />
|
||||||
|
<File Path="../package-debian.sh" />
|
||||||
|
<File Path="../package-osx.sh" />
|
||||||
|
<File Path="../package-release-zip.sh" />
|
||||||
|
<File Path="../pkg2appimage.yml" />
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/Solution Files/">
|
||||||
|
<File Path="Directory.Build.props" />
|
||||||
|
<File Path="Directory.Packages.props" />
|
||||||
|
</Folder>
|
||||||
|
<Project Path="AmazTool/AmazTool.csproj" />
|
||||||
|
<Project Path="GlobalHotKeys/src/GlobalHotKeys/GlobalHotKeys.csproj" />
|
||||||
|
<Project Path="ServiceLib/ServiceLib.csproj" />
|
||||||
|
<Project Path="v2rayN.Desktop/v2rayN.Desktop.csproj" />
|
||||||
|
<Project Path="v2rayN/v2rayN.csproj" />
|
||||||
|
</Solution>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user