mirror of
https://github.com/2dust/v2rayN.git
synced 2025-08-24 11:56:55 +00:00
Compare commits
4 commits
7b7fe0ef46
...
e104f9f9b2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e104f9f9b2 | ||
![]() |
876381a7fb | ||
![]() |
4f711b1bd3 | ||
![]() |
89893c0945 |
34 changed files with 1351 additions and 732 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -397,4 +397,5 @@ FodyWeavers.xsd
|
||||||
*.msp
|
*.msp
|
||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
*.sln.iml
|
*.sln.iml
|
511
package-rhel.sh
Normal file
511
package-rhel.sh
Normal file
|
@ -0,0 +1,511 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ===== Require Red Hat Enterprise Linux / Rocky Linux / AlmaLinux / CentOS Stream =======
|
||||||
|
if [[ -r /etc/os-release ]]; then
|
||||||
|
. /etc/os-release
|
||||||
|
case "$ID" in
|
||||||
|
rhel|rocky|almalinux|centos)
|
||||||
|
echo "[OK] Detected supported system: $NAME $VERSION_ID"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unsupported system: $NAME ($ID)."
|
||||||
|
echo "This script only supports Red Hat Enterprise Linux / Rocky Linux / AlmaLinux / CentOS Stream."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo "[ERROR] Cannot detect system (missing /etc/os-release)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Config & Parse arguments =========================================================
|
||||||
|
VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty
|
||||||
|
WITH_CORE="both" # Default: bundle both xray+sing-box
|
||||||
|
AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart)
|
||||||
|
FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads
|
||||||
|
|
||||||
|
# If the first argument starts with --, don’t treat it as version number
|
||||||
|
if [[ "${VERSION_ARG:-}" == --* ]]; then
|
||||||
|
VERSION_ARG=""
|
||||||
|
fi
|
||||||
|
# Take the first non --* argument as version, discard it
|
||||||
|
if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi
|
||||||
|
|
||||||
|
# Parse remaining optional arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--with-core) WITH_CORE="${2:-both}"; shift 2;;
|
||||||
|
--autostart) AUTOSTART=1; shift;;
|
||||||
|
--xray-ver) XRAY_VER="${2:-}"; shift 2;; # Specify xray version (optional)
|
||||||
|
--singbox-ver) SING_VER="${2:-}"; shift 2;; # Specify sing-box version (optional)
|
||||||
|
--netcore) FORCE_NETCORE=1; shift;; # NEW: force old mode (no bundle zip)
|
||||||
|
*)
|
||||||
|
if [[ -z "${VERSION_ARG:-}" ]]; then VERSION_ARG="$1"; fi
|
||||||
|
shift;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ===== Environment check ===============================================================
|
||||||
|
arch="$(uname -m)"
|
||||||
|
[[ "$arch" == "aarch64" || "$arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; }
|
||||||
|
|
||||||
|
# Dependencies (packaging shouldn’t be run as root, but this line needs sudo)
|
||||||
|
sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar || sudo dnf -y install dotnet-sdk
|
||||||
|
command -v curl >/dev/null
|
||||||
|
|
||||||
|
# Root directory = the script's location
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Git submodules (tolerant)
|
||||||
|
if [[ -f .gitmodules ]]; then
|
||||||
|
git submodule sync --recursive || true
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===== Locate project ================================================================
|
||||||
|
PROJECT="v2rayN.Desktop/v2rayN.Desktop.csproj"
|
||||||
|
if [[ ! -f "$PROJECT" ]]; then
|
||||||
|
PROJECT="$(find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true)"
|
||||||
|
fi
|
||||||
|
[[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; }
|
||||||
|
|
||||||
|
# ===== Resolve GUI version & auto checkout ============================================
|
||||||
|
# Rules:
|
||||||
|
# - If VERSION_ARG provided: try to checkout that tag (vX.Y.Z or X.Y.Z). If not found, ask which channel (Latest vs Pre-release),
|
||||||
|
# default to Latest, then fetch the chosen channel's latest tag and checkout.
|
||||||
|
# - If VERSION_ARG not provided: ask the channel first (default Latest), then checkout to that tag.
|
||||||
|
# - If not a git repo, warn and continue without switching (keep current branch).
|
||||||
|
VERSION="" # final GUI version string without 'v' prefix
|
||||||
|
|
||||||
|
choose_channel() {
|
||||||
|
# Print menu to stderr first, then read from stdin; only echo the chosen token to stdout.
|
||||||
|
local ch="latest" sel=""
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
>&2 echo "[?] Choose v2rayN release channel:"
|
||||||
|
>&2 echo " 1) Latest (stable) [default]"
|
||||||
|
>&2 echo " 2) Pre-release (preview)"
|
||||||
|
read -r -p "Enter 1 or 2 (default 1): " sel
|
||||||
|
case "${sel:-}" in
|
||||||
|
2) ch="prerelease" ;;
|
||||||
|
*) ch="latest" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
ch="latest"
|
||||||
|
fi
|
||||||
|
echo "$ch"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_latest_tag_latest() {
|
||||||
|
# Use GitHub API: /releases/latest → tag_name
|
||||||
|
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
|
||||||
|
| grep -Eo '"tag_name":\s*"v?[^"]+"' \
|
||||||
|
| head -n1 \
|
||||||
|
| sed -E 's/.*"tag_name":\s*"v?([^"]+)".*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_latest_tag_prerelease() {
|
||||||
|
# Use GitHub API: /releases?per_page=20 and pick the newest prerelease=true's tag_name
|
||||||
|
local json
|
||||||
|
json="$(curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20")" || return 1
|
||||||
|
echo "$json" \
|
||||||
|
| awk -v RS='},' '/"prerelease":[[:space:]]*true/ { if (match($0, /"tag_name":[[:space:]]*"v?[^"]+"/, m)) { t=m[0]; sub(/.*"tag_name":[[:space:]]*"?v?/, "", t); sub(/".*/, "", t); print t; exit } }'
|
||||||
|
}
|
||||||
|
|
||||||
|
git_try_checkout() {
|
||||||
|
# Try a series of refs and checkout when found.
|
||||||
|
# Args: version-like string (may contain leading 'v')
|
||||||
|
local want="$1" ref=""
|
||||||
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
git fetch --tags --force --prune --depth=1 || true
|
||||||
|
if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then
|
||||||
|
ref="v${want}"
|
||||||
|
elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then
|
||||||
|
ref="${want}"
|
||||||
|
elif git rev-parse --verify "${want}" >/dev/null 2>&1; then
|
||||||
|
ref="${want}"
|
||||||
|
fi
|
||||||
|
if [[ -n "$ref" ]]; then
|
||||||
|
echo "[OK] Found ref '${ref}', checking out..."
|
||||||
|
git checkout -f "${ref}"
|
||||||
|
if [[ -f .gitmodules ]]; then
|
||||||
|
git submodule sync --recursive || true
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
if [[ -n "${VERSION_ARG:-}" ]]; then
|
||||||
|
echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}"
|
||||||
|
if git_try_checkout "${VERSION_ARG#v}"; then
|
||||||
|
VERSION="${VERSION_ARG#v}"
|
||||||
|
else
|
||||||
|
echo "[WARN] Tag '${VERSION_ARG}' not found."
|
||||||
|
ch="$(choose_channel)"
|
||||||
|
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||||
|
tag=""
|
||||||
|
if [[ "$ch" == "prerelease" ]]; then
|
||||||
|
tag="$(get_latest_tag_prerelease || true)"
|
||||||
|
else
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||||
|
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||||
|
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||||
|
VERSION="${tag#v}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# No explicit GUI version passed: ask channel first
|
||||||
|
ch="$(choose_channel)"
|
||||||
|
echo "[*] Resolving ${ch} tag from GitHub releases..."
|
||||||
|
tag=""
|
||||||
|
if [[ "$ch" == "prerelease" ]]; then
|
||||||
|
tag="$(get_latest_tag_prerelease || true)"
|
||||||
|
else
|
||||||
|
tag="$(get_latest_tag_latest || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; }
|
||||||
|
echo "[*] Latest tag for '${ch}': ${tag}"
|
||||||
|
git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; }
|
||||||
|
VERSION="${tag#v}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree."
|
||||||
|
VERSION="${VERSION_ARG:-}"
|
||||||
|
if [[ -z "$VERSION" ]]; then
|
||||||
|
if git describe --tags --abbrev=0 >/dev/null 2>&1; then
|
||||||
|
VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
else
|
||||||
|
VERSION="0.0.0+git"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
fi
|
||||||
|
echo "[*] GUI version resolved as: ${VERSION}"
|
||||||
|
|
||||||
|
# ===== .NET publish (non-single file, self-contained) ===========================================
|
||||||
|
dotnet clean "$PROJECT" -c Release
|
||||||
|
rm -rf "$(dirname "$PROJECT")/bin/Release/net8.0" || true
|
||||||
|
|
||||||
|
dotnet restore "$PROJECT"
|
||||||
|
dotnet publish "$PROJECT" \
|
||||||
|
-c Release -r "$( [[ "$arch" == "aarch64" ]] && echo linux-arm64 || echo linux-x64 )" \
|
||||||
|
-p:PublishSingleFile=false \
|
||||||
|
-p:SelfContained=true \
|
||||||
|
-p:IncludeNativeLibrariesForSelfExtract=true
|
||||||
|
|
||||||
|
RID_DIR="$( [[ "$arch" == "aarch64" ]] && echo linux-arm64 || echo linux-x64 )"
|
||||||
|
PUBDIR="$(dirname "$PROJECT")/bin/Release/net8.0/${RID_DIR}/publish"
|
||||||
|
[[ -d "$PUBDIR" ]]
|
||||||
|
|
||||||
|
# ===== Download Core(Optional) ========================================================
|
||||||
|
download_xray() {
|
||||||
|
local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip"
|
||||||
|
mkdir -p "$outdir"
|
||||||
|
if [[ -z "$ver" ]]; then
|
||||||
|
# Latest version
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
|
||||||
|
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||||
|
fi
|
||||||
|
[[ -n "$ver" ]] || { echo "[xray] Failed to get version"; return 1; }
|
||||||
|
|
||||||
|
if [[ "$arch" == "aarch64" ]]; then
|
||||||
|
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-arm64-v8a.zip"
|
||||||
|
else
|
||||||
|
url="https://github.com/XTLS/Xray-core/releases/download/v${ver}/Xray-linux-64.zip"
|
||||||
|
fi
|
||||||
|
echo "[+] Download xray: $url"
|
||||||
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
|
||||||
|
curl -fL "$url" -o "$tmp/$zipname"
|
||||||
|
unzip -q "$tmp/$zipname" -d "$tmp"
|
||||||
|
install -Dm755 "$tmp/xray" "$outdir/xray"
|
||||||
|
}
|
||||||
|
|
||||||
|
download_singbox() {
|
||||||
|
local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin
|
||||||
|
mkdir -p "$outdir"
|
||||||
|
if [[ -z "$ver" ]]; then
|
||||||
|
ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
|
||||||
|
| grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true
|
||||||
|
fi
|
||||||
|
[[ -n "$ver" ]] || { echo "[sing-box] Failed to get version"; return 1; }
|
||||||
|
|
||||||
|
if [[ "$arch" == "aarch64" ]]; then
|
||||||
|
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-arm64.tar.gz"
|
||||||
|
else
|
||||||
|
url="https://github.com/SagerNet/sing-box/releases/download/v${ver}/sing-box-${ver}-linux-amd64.tar.gz"
|
||||||
|
fi
|
||||||
|
echo "[+] Download sing-box: $url"
|
||||||
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
|
||||||
|
curl -fL "$url" -o "$tmp/$tarname"
|
||||||
|
tar -C "$tmp" -xzf "$tmp/$tarname"
|
||||||
|
bin="$(find "$tmp" -type f -name 'sing-box' | head -n1 || true)"
|
||||||
|
[[ -n "$bin" ]] || { echo "[!] sing-box unpack failed"; return 1; }
|
||||||
|
install -Dm755 "$bin" "$outdir/sing-box"
|
||||||
|
}
|
||||||
|
|
||||||
|
# === Geo rule download (ZIP-LIKE LAYOUT for netcore mode) =====================
|
||||||
|
# Make netcore output match the ZIP bundle structure:
|
||||||
|
# - All geo databases at outroot/bin/ (geosite.dat, geoip.dat, Country.mmdb, geoip-only-cn-private.dat, geoip.metadb)
|
||||||
|
# - All *.srs rule-sets at outroot/bin/srss/
|
||||||
|
# (Binaries stay under outroot/bin/xray and outroot/bin/sing_box as before.)
|
||||||
|
download_geo_assets() {
|
||||||
|
local outroot="$1"
|
||||||
|
local bin_dir="$outroot/bin"
|
||||||
|
local srss_dir="$bin_dir/srss"
|
||||||
|
mkdir -p "$bin_dir" "$srss_dir"
|
||||||
|
|
||||||
|
echo "[+] Download Xray Geo (geosite/geoip/...) to ${bin_dir}"
|
||||||
|
curl -fsSL -o "$bin_dir/geosite.dat" \
|
||||||
|
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip.dat" \
|
||||||
|
"https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip-only-cn-private.dat" \
|
||||||
|
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
|
||||||
|
curl -fsSL -o "$bin_dir/Country.mmdb" \
|
||||||
|
"https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
|
||||||
|
|
||||||
|
echo "[+] Download sing-box rule DB & rule-sets to ZIP-like paths"
|
||||||
|
curl -fsSL -o "$bin_dir/geoip.metadb" \
|
||||||
|
"https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb" || true
|
||||||
|
|
||||||
|
for f in \
|
||||||
|
geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs \
|
||||||
|
geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
|
||||||
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geoip/$f" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in \
|
||||||
|
geosite-cn.srs geosite-gfw.srs geosite-greatfire.srs \
|
||||||
|
geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
|
||||||
|
curl -fsSL -o "$srss_dir/$f" \
|
||||||
|
"https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# === NEW: Prefer the all-in-one v2rayN bundle zip first =======================
|
||||||
|
download_v2rayn_bundle() {
|
||||||
|
local outroot="$1"
|
||||||
|
local url=""
|
||||||
|
if [[ "$arch" == "aarch64" ]]; then
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-arm64.zip"
|
||||||
|
else
|
||||||
|
url="https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-64.zip"
|
||||||
|
fi
|
||||||
|
echo "[+] Try v2rayN bundle archive: $url"
|
||||||
|
local tmp zipname
|
||||||
|
tmp="$(mktemp -d)"; zipname="$tmp/v2rayn.zip"
|
||||||
|
curl -fL "$url" -o "$zipname" || { echo "[!] Bundle download failed"; return 1; }
|
||||||
|
unzip -q "$zipname" -d "$tmp" || { echo "[!] Bundle unzip failed"; return 1; }
|
||||||
|
|
||||||
|
# Normalize layout: copy 'bin/' if present; otherwise copy all
|
||||||
|
if [[ -d "$tmp/bin" ]]; then
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
rsync -a "$tmp/bin/" "$outroot/bin/"
|
||||||
|
else
|
||||||
|
rsync -a "$tmp/" "$outroot/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- CLEANUPS (keep bundle-only adjustments) -------------------------------
|
||||||
|
rm -f "$outroot/v2rayn.zip" 2>/dev/null || true
|
||||||
|
find "$outroot" -type d -name "mihomo" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
local nested_dir
|
||||||
|
nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)"
|
||||||
|
if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then
|
||||||
|
mkdir -p "$outroot/bin"
|
||||||
|
rsync -a "$nested_dir/bin/" "$outroot/bin/"
|
||||||
|
rm -rf "$nested_dir"
|
||||||
|
fi
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
echo "[+] Bundle extracted to $outroot"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== Copy publish files to RPM build root ==================================================
|
||||||
|
rpmdev-setuptree
|
||||||
|
TOPDIR="${HOME}/rpmbuild"
|
||||||
|
SPECDIR="${TOPDIR}/SPECS"
|
||||||
|
SOURCEDIR="${TOPDIR}/SOURCES"
|
||||||
|
|
||||||
|
PKGROOT="v2rayN-publish"
|
||||||
|
WORKDIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$WORKDIR"' EXIT
|
||||||
|
|
||||||
|
mkdir -p "$WORKDIR/$PKGROOT"
|
||||||
|
cp -a "$PUBDIR/." "$WORKDIR/$PKGROOT/"
|
||||||
|
|
||||||
|
# icon(Optional)
|
||||||
|
ICON_CANDIDATE="$(dirname "$PROJECT")/../v2rayN.Desktop/v2rayN.png"
|
||||||
|
[[ -f "$ICON_CANDIDATE" ]] && cp "$ICON_CANDIDATE" "$WORKDIR/$PKGROOT/v2rayn.png" || true
|
||||||
|
|
||||||
|
# bin directory structure
|
||||||
|
mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box"
|
||||||
|
|
||||||
|
# ====== NEW decision: prefer bundle zip unless --netcore, else fall back ======
|
||||||
|
if [[ "$FORCE_NETCORE" -eq 0 ]]; then
|
||||||
|
if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then
|
||||||
|
echo "[*] Using v2rayN bundle archive."
|
||||||
|
else
|
||||||
|
echo "[*] Bundle failed, fallback to separate core + rules."
|
||||||
|
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||||
|
fi
|
||||||
|
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
|
fi
|
||||||
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[*] --netcore specified: use separate core + rules."
|
||||||
|
if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)"
|
||||||
|
fi
|
||||||
|
if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then
|
||||||
|
download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)"
|
||||||
|
fi
|
||||||
|
download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar -C "$WORKDIR" -czf "$SOURCEDIR/$PKGROOT.tar.gz" "$PKGROOT"
|
||||||
|
|
||||||
|
# ===== Generate SPEC (heredoc with placeholders) ===================================
|
||||||
|
SPECFILE="$SPECDIR/v2rayN.spec"
|
||||||
|
cat > "$SPECFILE" <<'SPEC'
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%undefine _debuginfo_subpackages
|
||||||
|
%undefine _debugsource_packages
|
||||||
|
# Ignore outdated LTTng dependencies incorrectly reported by the .NET runtime (to avoid installation failures)
|
||||||
|
%global __requires_exclude ^liblttng-ust\.so\..*$
|
||||||
|
|
||||||
|
Name: v2rayN
|
||||||
|
Version: __VERSION__
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: v2rayN (Avalonia) GUI client for Linux (x86_64/aarch64)
|
||||||
|
License: GPL-3.0-only
|
||||||
|
URL: https://github.com/2dust/v2rayN
|
||||||
|
ExclusiveArch: aarch64 x86_64
|
||||||
|
Source0: __PKGROOT__.tar.gz
|
||||||
|
|
||||||
|
# Runtime dependencies (Avalonia / X11 / Fonts / GL)
|
||||||
|
Requires: libX11, libXrandr, libXcursor, libXi, libXext, libxcb, libXrender, libXfixes, libXinerama, libxkbcommon
|
||||||
|
Requires: fontconfig, freetype, cairo, pango, mesa-libEGL, mesa-libGL
|
||||||
|
|
||||||
|
%description
|
||||||
|
v2rayN GUI client built with Avalonia.
|
||||||
|
Installs self-contained publish under /opt/v2rayN and a launcher 'v2rayn'.
|
||||||
|
Cores (if bundled): /opt/v2rayN/bin/xray, /opt/v2rayN/bin/sing_box.
|
||||||
|
Geo files for Xray are placed at /opt/v2rayN/bin/xray; launcher will symlink them into user's XDG data dir on first run.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q -n __PKGROOT__
|
||||||
|
|
||||||
|
%build
|
||||||
|
# no build
|
||||||
|
|
||||||
|
%install
|
||||||
|
install -dm0755 %{buildroot}/opt/v2rayN
|
||||||
|
cp -a * %{buildroot}/opt/v2rayN/
|
||||||
|
|
||||||
|
# Launcher (prioritize ELF first, then fall back to DLL; also create Geo symlinks for the user)
|
||||||
|
install -dm0755 %{buildroot}%{_bindir}
|
||||||
|
cat > %{buildroot}%{_bindir}/v2rayn << 'EOF'
|
||||||
|
#!/usr/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
DIR="/opt/v2rayN"
|
||||||
|
|
||||||
|
# --- SYMLINK GEO into user's XDG dir (new) ---
|
||||||
|
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 ELF(apphost)
|
||||||
|
if [[ -x "$DIR/v2rayN" ]]; then exec "$DIR/v2rayN" "$@"; fi
|
||||||
|
|
||||||
|
# DLL fallback (for framework-dependent publish)
|
||||||
|
for dll in v2rayN.Desktop.dll v2rayN.dll; do
|
||||||
|
if [[ -f "$DIR/$dll" ]]; then exec /usr/bin/dotnet "$DIR/$dll" "$@"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "v2rayN launcher: no executable found in $DIR" >&2
|
||||||
|
ls -l "$DIR" >&2 || true
|
||||||
|
exit 1
|
||||||
|
EOF
|
||||||
|
chmod 0755 %{buildroot}%{_bindir}/v2rayn
|
||||||
|
|
||||||
|
# Desktop File
|
||||||
|
install -dm0755 %{buildroot}%{_datadir}/applications
|
||||||
|
cat > %{buildroot}%{_datadir}/applications/v2rayn.desktop << 'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=v2rayN
|
||||||
|
Comment=GUI client for Xray / sing-box
|
||||||
|
Exec=v2rayn
|
||||||
|
Icon=v2rayn
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# icon
|
||||||
|
if [ -f "%{_builddir}/__PKGROOT__/v2rayn.png" ]; then
|
||||||
|
install -dm0755 %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
|
||||||
|
install -m0644 %{_builddir}/__PKGROOT__/v2rayn.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
fi
|
||||||
|
|
||||||
|
%post
|
||||||
|
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
|
||||||
|
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
%postun
|
||||||
|
/usr/bin/update-desktop-database %{_datadir}/applications >/dev/null 2>&1 || true
|
||||||
|
/usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
%files
|
||||||
|
%{_bindir}/v2rayn
|
||||||
|
/opt/v2rayN
|
||||||
|
%{_datadir}/applications/v2rayn.desktop
|
||||||
|
%{_datadir}/icons/hicolor/256x256/apps/v2rayn.png
|
||||||
|
SPEC
|
||||||
|
|
||||||
|
# Optional: system-wide autostart (append block, keep original logic unchanged)
|
||||||
|
if [[ "$AUTOSTART" -eq 1 ]]; then
|
||||||
|
cat >> "$SPECFILE" <<'SPEC'
|
||||||
|
# System-wide autostart entry
|
||||||
|
%install
|
||||||
|
install -dm0755 %{buildroot}/etc/xdg/autostart
|
||||||
|
cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << 'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=v2rayN (Autostart)
|
||||||
|
Exec=v2rayn
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
|
NoDisplay=false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
%files
|
||||||
|
%config(noreplace) /etc/xdg/autostart/v2rayn.desktop
|
||||||
|
SPEC
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Injecting version/package root placeholders
|
||||||
|
sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE"
|
||||||
|
sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE"
|
||||||
|
|
||||||
|
# ===== Build RPM ================================================================
|
||||||
|
rpmbuild -ba "$SPECFILE"
|
||||||
|
|
||||||
|
echo "Build done. RPM at:"
|
||||||
|
ls -1 "${TOPDIR}/RPMS/$( [[ "$arch" == "aarch64" ]] && echo aarch64 || echo x86_64 )/v2rayN-${VERSION}-1"*.rpm
|
|
@ -289,6 +289,31 @@ public class Global
|
||||||
"sing_box"
|
"sing_box"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> XraySupportConfigType =
|
||||||
|
[
|
||||||
|
EConfigType.VMess,
|
||||||
|
EConfigType.VLESS,
|
||||||
|
EConfigType.Shadowsocks,
|
||||||
|
EConfigType.Trojan,
|
||||||
|
EConfigType.WireGuard,
|
||||||
|
EConfigType.SOCKS,
|
||||||
|
EConfigType.HTTP,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly HashSet<EConfigType> SingboxSupportConfigType =
|
||||||
|
[
|
||||||
|
EConfigType.VMess,
|
||||||
|
EConfigType.VLESS,
|
||||||
|
EConfigType.Shadowsocks,
|
||||||
|
EConfigType.Trojan,
|
||||||
|
EConfigType.Hysteria2,
|
||||||
|
EConfigType.TUIC,
|
||||||
|
EConfigType.Anytls,
|
||||||
|
EConfigType.WireGuard,
|
||||||
|
EConfigType.SOCKS,
|
||||||
|
EConfigType.HTTP,
|
||||||
|
];
|
||||||
|
|
||||||
public static readonly List<string> DomainStrategies =
|
public static readonly List<string> DomainStrategies =
|
||||||
[
|
[
|
||||||
AsIs,
|
AsIs,
|
||||||
|
|
28
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
28
v2rayN/ServiceLib/Resx/ResUI.Designer.cs
generated
|
@ -3732,6 +3732,34 @@ namespace ServiceLib.Resx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Auto Route 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunAutoRoute {
|
||||||
|
get { return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Strict Route 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunStrictRoute {
|
||||||
|
get { return ResourceManager.GetString("TbSettingsTunStrictRoute", resourceCulture); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Stack 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunStack {
|
||||||
|
get { return ResourceManager.GetString("TbSettingsTunStack", resourceCulture); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 MTU 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string TbSettingsTunMtu {
|
||||||
|
get { return ResourceManager.GetString("TbSettingsTunMtu", resourceCulture); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 Tun Mode settings 的本地化字符串。
|
/// 查找类似 Tun Mode settings 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1059,6 +1059,18 @@
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
|
<value>لطفاً مطمئن شوید که ملاحظات وجود دارند و منحصر به فرد هستند</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>مسیریابی خودکار</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>مسیریابی سختگیرانه</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>پشته شبکه</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>فعال سازی additional Inbound</value>
|
<value>فعال سازی additional Inbound</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1059,6 +1059,18 @@
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek</value>
|
<value>Kérjük, győződjön meg arról, hogy a konfigurációs megjegyzések léteznek és egyediek</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>Automatikus útválasztás</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>Szigorú útválasztás</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>Hálózati verem</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>További bejövő engedélyezése</value>
|
<value>További bejövő engedélyezése</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1059,6 +1059,18 @@
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Please make sure the Configuration remarks exist and are unique</value>
|
<value>Please make sure the Configuration remarks exist and are unique</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>Auto Route</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>Strict Route</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>Stack</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>Enable additional Inbound</value>
|
<value>Enable additional Inbound</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1059,6 +1059,18 @@
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>Убедитесь, что примечание существует и является уникальным</value>
|
<value>Убедитесь, что примечание существует и является уникальным</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>Автоматическая маршрутизация</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>Строгая маршрутизация</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>Сетевой стек</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>Включить дополнительный входящий канал</value>
|
<value>Включить дополнительный входящий канал</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1056,6 +1056,18 @@
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>请确保配置文件别名存在并唯一</value>
|
<value>请确保配置文件别名存在并唯一</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>自动路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>严格路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>协议栈</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>启用额外监听端口</value>
|
<value>启用额外监听端口</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1056,6 +1056,18 @@
|
||||||
<data name="LvPrevProfileTip" xml:space="preserve">
|
<data name="LvPrevProfileTip" xml:space="preserve">
|
||||||
<value>請確保設定檔別名存在並且唯一</value>
|
<value>請確保設定檔別名存在並且唯一</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TbSettingsTunAutoRoute" xml:space="preserve">
|
||||||
|
<value>自動路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStrictRoute" xml:space="preserve">
|
||||||
|
<value>嚴格路由</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunStack" xml:space="preserve">
|
||||||
|
<value>協定堆疊</value>
|
||||||
|
</data>
|
||||||
|
<data name="TbSettingsTunMtu" xml:space="preserve">
|
||||||
|
<value>MTU</value>
|
||||||
|
</data>
|
||||||
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
<data name="TbSettingsEnableExInbound" xml:space="preserve">
|
||||||
<value>啟用額外偵聽連接埠</value>
|
<value>啟用額外偵聽連接埠</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -133,7 +133,7 @@ public partial class CoreConfigSingboxService(Config config)
|
||||||
|
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -381,7 +381,7 @@ public partial class CoreConfigSingboxService(Config config)
|
||||||
var proxyProfiles = new List<ProfileItem>();
|
var proxyProfiles = new List<ProfileItem>();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (!Global.SingboxSupportConfigType.Contains(it.ConfigType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,7 +372,7 @@ public partial class CoreConfigSingboxService
|
||||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
string? prevOutboundTag = null;
|
string? prevOutboundTag = null;
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom)
|
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
|
||||||
{
|
{
|
||||||
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
prevOutboundTag = $"prev-{Global.ProxyTag}";
|
||||||
var prevServer = await GenServer(prevNode);
|
var prevServer = await GenServer(prevNode);
|
||||||
|
@ -463,7 +463,7 @@ public partial class CoreConfigSingboxService
|
||||||
{
|
{
|
||||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom)
|
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType))
|
||||||
{
|
{
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbound4Sbox>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
@ -558,7 +558,7 @@ public partial class CoreConfigSingboxService
|
||||||
// Next proxy
|
// Next proxy
|
||||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
if (nextNode is not null
|
if (nextNode is not null
|
||||||
&& nextNode.ConfigType != EConfigType.Custom)
|
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType))
|
||||||
{
|
{
|
||||||
nextOutbound ??= await GenServer(nextNode);
|
nextOutbound ??= await GenServer(nextNode);
|
||||||
nextOutbound.tag = outbound.tag;
|
nextOutbound.tag = outbound.tag;
|
||||||
|
|
|
@ -338,7 +338,7 @@ public partial class CoreConfigSingboxService
|
||||||
|
|
||||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||||
if (node == null
|
if (node == null
|
||||||
|| node.ConfigType == EConfigType.Custom)
|
|| !Global.SingboxSupportConfigType.Contains(node.ConfigType))
|
||||||
{
|
{
|
||||||
return Global.ProxyTag;
|
return Global.ProxyTag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,11 +110,7 @@ public partial class CoreConfigV2rayService(Config config)
|
||||||
var proxyProfiles = new List<ProfileItem>();
|
var proxyProfiles = new List<ProfileItem>();
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (it.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -250,7 +246,7 @@ public partial class CoreConfigV2rayService(Config config)
|
||||||
|
|
||||||
foreach (var it in selecteds)
|
foreach (var it in selecteds)
|
||||||
{
|
{
|
||||||
if (it.ConfigType == EConfigType.Custom)
|
if (!Global.XraySupportConfigType.Contains(it.ConfigType))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,9 +187,9 @@ public partial class CoreConfigV2rayService
|
||||||
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
|
foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile })
|
||||||
{
|
{
|
||||||
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
|
var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile);
|
||||||
if (profileNode is not null &&
|
if (profileNode is not null
|
||||||
profileNode.ConfigType is not (EConfigType.Custom or EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls) &&
|
&& Global.XraySupportConfigType.Contains(profileNode.ConfigType)
|
||||||
Utils.IsDomain(profileNode.Address))
|
&& Utils.IsDomain(profileNode.Address))
|
||||||
{
|
{
|
||||||
directDomainList.Add(profileNode.Address);
|
directDomainList.Add(profileNode.Address);
|
||||||
}
|
}
|
||||||
|
@ -217,11 +217,11 @@ public partial class CoreConfigV2rayService
|
||||||
AddDnsServers(directDNSAddress, directGeositeList);
|
AddDnsServers(directDNSAddress, directGeositeList);
|
||||||
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
|
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
|
||||||
|
|
||||||
var useDirectDns = rules?.LastOrDefault() is { } lastRule &&
|
var useDirectDns = rules?.LastOrDefault() is { } lastRule
|
||||||
lastRule.OutboundTag == Global.DirectTag &&
|
&& lastRule.OutboundTag == Global.DirectTag
|
||||||
(lastRule.Port == "0-65535" ||
|
&& (lastRule.Port == "0-65535"
|
||||||
lastRule.Network == "tcp,udp" ||
|
|| lastRule.Network == "tcp,udp"
|
||||||
lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
|
||||||
|
|
||||||
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
|
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
|
||||||
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
|
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
|
||||||
|
@ -391,9 +391,7 @@ public partial class CoreConfigV2rayService
|
||||||
// Previous proxy
|
// Previous proxy
|
||||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom
|
&& Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)
|
||||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& prevNode.ConfigType != EConfigType.TUIC
|
|
||||||
&& Utils.IsDomain(prevNode.Address))
|
&& Utils.IsDomain(prevNode.Address))
|
||||||
{
|
{
|
||||||
domainList.Add(prevNode.Address);
|
domainList.Add(prevNode.Address);
|
||||||
|
@ -402,9 +400,7 @@ public partial class CoreConfigV2rayService
|
||||||
// Next proxy
|
// Next proxy
|
||||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
if (nextNode is not null
|
if (nextNode is not null
|
||||||
&& nextNode.ConfigType != EConfigType.Custom
|
&& Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)
|
||||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& nextNode.ConfigType != EConfigType.TUIC
|
|
||||||
&& Utils.IsDomain(nextNode.Address))
|
&& Utils.IsDomain(nextNode.Address))
|
||||||
{
|
{
|
||||||
domainList.Add(nextNode.Address);
|
domainList.Add(nextNode.Address);
|
||||||
|
|
|
@ -529,10 +529,7 @@ public partial class CoreConfigV2rayService
|
||||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
string? prevOutboundTag = null;
|
string? prevOutboundTag = null;
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom
|
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
|
||||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& prevNode.ConfigType != EConfigType.TUIC
|
|
||||||
&& prevNode.ConfigType != EConfigType.Anytls)
|
|
||||||
{
|
{
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
@ -605,10 +602,7 @@ public partial class CoreConfigV2rayService
|
||||||
{
|
{
|
||||||
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile);
|
||||||
if (prevNode is not null
|
if (prevNode is not null
|
||||||
&& prevNode.ConfigType != EConfigType.Custom
|
&& Global.XraySupportConfigType.Contains(prevNode.ConfigType))
|
||||||
&& prevNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& prevNode.ConfigType != EConfigType.TUIC
|
|
||||||
&& prevNode.ConfigType != EConfigType.Anytls)
|
|
||||||
{
|
{
|
||||||
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
var prevOutbound = JsonUtils.Deserialize<Outbounds4Ray>(txtOutbound);
|
||||||
await GenOutbound(prevNode, prevOutbound);
|
await GenOutbound(prevNode, prevOutbound);
|
||||||
|
@ -675,10 +669,7 @@ public partial class CoreConfigV2rayService
|
||||||
// Next proxy
|
// Next proxy
|
||||||
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile);
|
||||||
if (nextNode is not null
|
if (nextNode is not null
|
||||||
&& nextNode.ConfigType != EConfigType.Custom
|
&& Global.XraySupportConfigType.Contains(nextNode.ConfigType))
|
||||||
&& nextNode.ConfigType != EConfigType.Hysteria2
|
|
||||||
&& nextNode.ConfigType != EConfigType.TUIC
|
|
||||||
&& nextNode.ConfigType != EConfigType.Anytls)
|
|
||||||
{
|
{
|
||||||
if (nextOutbound == null)
|
if (nextOutbound == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -126,10 +126,7 @@ public partial class CoreConfigV2rayService
|
||||||
|
|
||||||
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag);
|
||||||
if (node == null
|
if (node == null
|
||||||
|| node.ConfigType == EConfigType.Custom
|
|| !Global.XraySupportConfigType.Contains(node.ConfigType))
|
||||||
|| node.ConfigType == EConfigType.Hysteria2
|
|
||||||
|| node.ConfigType == EConfigType.TUIC
|
|
||||||
|| node.ConfigType == EConfigType.Anytls)
|
|
||||||
{
|
{
|
||||||
return Global.ProxyTag;
|
return Global.ProxyTag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,8 +356,8 @@ public class SpeedtestService
|
||||||
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
private List<List<ServerTestItem>> GetTestBatchItem(List<ServerTestItem> lstSelected, int pageSize)
|
||||||
{
|
{
|
||||||
List<List<ServerTestItem>> lstTest = new();
|
List<List<ServerTestItem>> lstTest = new();
|
||||||
var lst1 = lstSelected.Where(t => t.ConfigType is not (EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls)).ToList();
|
var lst1 = lstSelected.Where(t => Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||||
var lst2 = lstSelected.Where(t => t.ConfigType is EConfigType.Hysteria2 or EConfigType.TUIC or EConfigType.Anytls).ToList();
|
var lst2 = lstSelected.Where(t => Global.SingboxSupportConfigType.Contains(t.ConfigType) && !Global.XraySupportConfigType.Contains(t.ConfigType)).ToList();
|
||||||
|
|
||||||
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
|
for (var num = 0; num < (int)Math.Ceiling(lst1.Count * 1.0 / pageSize); num++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,12 +4,12 @@ using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Win32.Input;
|
using Avalonia.Win32.Input;
|
||||||
using GlobalHotKeys;
|
using GlobalHotKeys;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Handler;
|
namespace v2rayN.Desktop.Manager;
|
||||||
|
|
||||||
public sealed class HotkeyHandler
|
public sealed class HotkeyManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<HotkeyHandler> _instance = new(() => new());
|
private static readonly Lazy<HotkeyManager> _instance = new(() => new());
|
||||||
public static HotkeyHandler Instance = _instance.Value;
|
public static HotkeyManager Instance = _instance.Value;
|
||||||
private readonly Dictionary<int, EGlobalHotkey> _hotkeyTriggerDic = new();
|
private readonly Dictionary<int, EGlobalHotkey> _hotkeyTriggerDic = new();
|
||||||
private HotKeyManager? _hotKeyManager;
|
private HotKeyManager? _hotKeyManager;
|
||||||
|
|
|
@ -523,7 +523,7 @@
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Mtu" />
|
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtShortId9"
|
x:Name="txtShortId9"
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
|
|
|
@ -5,7 +5,7 @@ using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
using v2rayN.Desktop.Handler;
|
using v2rayN.Desktop.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ public partial class GlobalHotkeySettingWindow : WindowBase<GlobalHotkeySettingV
|
||||||
|
|
||||||
btnReset.Click += btnReset_Click;
|
btnReset.Click += btnReset_Click;
|
||||||
|
|
||||||
HotkeyHandler.Instance.IsPause = true;
|
HotkeyManager.Instance.IsPause = true;
|
||||||
this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
|
this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false;
|
||||||
btnCancel.Click += (s, e) => this.Close();
|
btnCancel.Click += (s, e) => this.Close();
|
||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
|
|
|
@ -13,7 +13,7 @@ using ServiceLib.Manager;
|
||||||
using Splat;
|
using Splat;
|
||||||
using v2rayN.Desktop.Base;
|
using v2rayN.Desktop.Base;
|
||||||
using v2rayN.Desktop.Common;
|
using v2rayN.Desktop.Common;
|
||||||
using v2rayN.Desktop.Handler;
|
using v2rayN.Desktop.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Desktop.Views;
|
namespace v2rayN.Desktop.Views;
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||||
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
|
||||||
|
|
||||||
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
|
ThreadPool.RegisterWaitForSingleObject(Program.ProgramStarted, OnProgramStarted, null, -1, false);
|
||||||
HotkeyHandler.Instance.Init(_config, OnHotkeyHandler);
|
HotkeyManager.Instance.Init(_config, OnHotkeyHandler);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -235,7 +235,7 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
|
||||||
StorageUI();
|
StorageUI();
|
||||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
HotkeyHandler.Instance.Dispose();
|
HotkeyManager.Instance.Dispose();
|
||||||
desktop.Shutdown();
|
desktop.Shutdown();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -736,7 +736,7 @@
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Auto Route" />
|
Text="{x:Static resx:ResUI.TbSettingsTunAutoRoute}" />
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
x:Name="togAutoRoute"
|
x:Name="togAutoRoute"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
@ -749,7 +749,7 @@
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Strict Route" />
|
Text="{x:Static resx:ResUI.TbSettingsTunStrictRoute}" />
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
x:Name="togStrictRoute"
|
x:Name="togStrictRoute"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
|
@ -762,7 +762,7 @@
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Stack" />
|
Text="{x:Static resx:ResUI.TbSettingsTunStack}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbStack"
|
x:Name="cmbStack"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
|
@ -776,7 +776,7 @@
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Mtu" />
|
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbMtu"
|
x:Name="cmbMtu"
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
|
|
|
@ -7,7 +7,7 @@ using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace v2rayN;
|
namespace v2rayN;
|
||||||
|
|
||||||
public class QRCodeHelper
|
public class QRCodeUtils
|
||||||
{
|
{
|
||||||
public static ImageSource? GetQRCode(string? strContent)
|
public static ImageSource? GetQRCode(string? strContent)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ public class QRCodeHelper
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var qrCodeImage = QRCodeUtils.GenQRCode(strContent);
|
var qrCodeImage = ServiceLib.Common.QRCodeUtils.GenQRCode(strContent);
|
||||||
return qrCodeImage is null ? null : ByteToImage(qrCodeImage);
|
return qrCodeImage is null ? null : ByteToImage(qrCodeImage);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
|
@ -6,12 +6,12 @@ using System.Windows.Input;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using ServiceLib.Manager;
|
using ServiceLib.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Handler;
|
namespace v2rayN.Manager;
|
||||||
|
|
||||||
public sealed class HotkeyHandler
|
public sealed class HotkeyManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<HotkeyHandler> _instance = new(() => new());
|
private static readonly Lazy<HotkeyManager> _instance = new(() => new());
|
||||||
public static HotkeyHandler Instance = _instance.Value;
|
public static HotkeyManager Instance = _instance.Value;
|
||||||
private const int WmHotkey = 0x0312;
|
private const int WmHotkey = 0x0312;
|
||||||
private readonly Dictionary<int, List<EGlobalHotkey>> _hotkeyTriggerDic = new();
|
private readonly Dictionary<int, List<EGlobalHotkey>> _hotkeyTriggerDic = new();
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ public sealed class HotkeyHandler
|
||||||
|
|
||||||
public event Action<EGlobalHotkey>? HotkeyTriggerEvent;
|
public event Action<EGlobalHotkey>? HotkeyTriggerEvent;
|
||||||
|
|
||||||
public HotkeyHandler()
|
public HotkeyManager()
|
||||||
{
|
{
|
||||||
ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage;
|
ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage;
|
||||||
Init();
|
Init();
|
||||||
|
@ -51,7 +51,7 @@ public sealed class HotkeyHandler
|
||||||
modifiers |= KeyModifiers.Alt;
|
modifiers |= KeyModifiers.Alt;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = (key << 16) | (int)modifiers;
|
key = key << 16 | (int)modifiers;
|
||||||
if (!_hotkeyTriggerDic.ContainsKey(key))
|
if (!_hotkeyTriggerDic.ContainsKey(key))
|
||||||
{
|
{
|
||||||
_hotkeyTriggerDic.Add(key, new() { item.EGlobalHotkey });
|
_hotkeyTriggerDic.Add(key, new() { item.EGlobalHotkey });
|
||||||
|
@ -77,7 +77,7 @@ public sealed class HotkeyHandler
|
||||||
|
|
||||||
Application.Current?.Dispatcher.Invoke(() =>
|
Application.Current?.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
isSuccess = RegisterHotKey(IntPtr.Zero, _hotkeyCode, hotkeyInfo.fsModifiers, hotkeyInfo.vKey);
|
isSuccess = RegisterHotKey(nint.Zero, _hotkeyCode, hotkeyInfo.fsModifiers, hotkeyInfo.vKey);
|
||||||
});
|
});
|
||||||
foreach (var name in hotkeyInfo.Names)
|
foreach (var name in hotkeyInfo.Names)
|
||||||
{
|
{
|
||||||
|
@ -101,7 +101,7 @@ public sealed class HotkeyHandler
|
||||||
{
|
{
|
||||||
Application.Current?.Dispatcher.Invoke(() =>
|
Application.Current?.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
UnregisterHotKey(IntPtr.Zero, hotkey);
|
UnregisterHotKey(nint.Zero, hotkey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Init();
|
Init();
|
||||||
|
@ -111,7 +111,7 @@ public sealed class HotkeyHandler
|
||||||
private (int fsModifiers, int vKey, string hotkeyStr, List<string> Names) GetHotkeyInfo(int hotkeyCode)
|
private (int fsModifiers, int vKey, string hotkeyStr, List<string> Names) GetHotkeyInfo(int hotkeyCode)
|
||||||
{
|
{
|
||||||
var fsModifiers = hotkeyCode & 0xffff;
|
var fsModifiers = hotkeyCode & 0xffff;
|
||||||
var vKey = (hotkeyCode >> 16) & 0xffff;
|
var vKey = hotkeyCode >> 16 & 0xffff;
|
||||||
var hotkeyStr = new StringBuilder();
|
var hotkeyStr = new StringBuilder();
|
||||||
var names = new List<string>();
|
var names = new List<string>();
|
||||||
|
|
||||||
|
@ -174,10 +174,10 @@ public sealed class HotkeyHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
|
private static extern bool RegisterHotKey(nint hWnd, int id, int fsModifiers, int vlc);
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
private static extern bool UnregisterHotKey(nint hWnd, int id);
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
private enum KeyModifiers
|
private enum KeyModifiers
|
|
@ -1,13 +1,14 @@
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using v2rayN.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Handler;
|
namespace v2rayN.Manager;
|
||||||
|
|
||||||
public sealed class WindowsHandler
|
public sealed class WindowsManager
|
||||||
{
|
{
|
||||||
private static readonly Lazy<WindowsHandler> instance = new(() => new());
|
private static readonly Lazy<WindowsManager> instance = new(() => new());
|
||||||
public static WindowsHandler Instance => instance.Value;
|
public static WindowsManager Instance => instance.Value;
|
||||||
private static readonly string _tag = "WindowsHandler";
|
private static readonly string _tag = "WindowsHandler";
|
||||||
|
|
||||||
public async Task<Icon> GetNotifyIcon(Config config)
|
public async Task<Icon> GetNotifyIcon(Config config)
|
||||||
|
@ -97,8 +98,8 @@ public sealed class WindowsHandler
|
||||||
|
|
||||||
public void RegisterGlobalHotkey(Config config, Action<EGlobalHotkey> handler, Action<bool, string>? update)
|
public void RegisterGlobalHotkey(Config config, Action<EGlobalHotkey> handler, Action<bool, string>? update)
|
||||||
{
|
{
|
||||||
HotkeyHandler.Instance.UpdateViewEvent += update;
|
HotkeyManager.Instance.UpdateViewEvent += update;
|
||||||
HotkeyHandler.Instance.HotkeyTriggerEvent += handler;
|
HotkeyManager.Instance.HotkeyTriggerEvent += handler;
|
||||||
HotkeyHandler.Instance.Load();
|
HotkeyManager.Instance.Load();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -696,7 +696,7 @@
|
||||||
Margin="{StaticResource Margin4}"
|
Margin="{StaticResource Margin4}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="Mtu" />
|
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="txtShortId9"
|
x:Name="txtShortId9"
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
using ServiceLib.Manager;
|
||||||
using v2rayN.Handler;
|
using v2rayN.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Views;
|
namespace v2rayN.Views;
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ public partial class GlobalHotkeySettingWindow
|
||||||
|
|
||||||
btnReset.Click += btnReset_Click;
|
btnReset.Click += btnReset_Click;
|
||||||
|
|
||||||
HotkeyHandler.Instance.IsPause = true;
|
HotkeyManager.Instance.IsPause = true;
|
||||||
this.Closing += (s, e) => HotkeyHandler.Instance.IsPause = false;
|
this.Closing += (s, e) => HotkeyManager.Instance.IsPause = false;
|
||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
Height="700"
|
Height="700"
|
||||||
MinWidth="900"
|
MinWidth="900"
|
||||||
x:TypeArguments="vms:MainWindowViewModel"
|
x:TypeArguments="vms:MainWindowViewModel"
|
||||||
|
Icon="/Resources/v2rayN.ico"
|
||||||
ResizeMode="CanResizeWithGrip"
|
ResizeMode="CanResizeWithGrip"
|
||||||
ShowInTaskbar="True"
|
ShowInTaskbar="True"
|
||||||
Style="{StaticResource WindowGlobal}"
|
Style="{StaticResource WindowGlobal}"
|
||||||
|
|
|
@ -10,7 +10,7 @@ using MaterialDesignThemes.Wpf;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
using ServiceLib.Manager;
|
||||||
using Splat;
|
using Splat;
|
||||||
using v2rayN.Handler;
|
using v2rayN.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Views;
|
namespace v2rayN.Views;
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ public partial class MainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
AddHelpMenuItem();
|
AddHelpMenuItem();
|
||||||
WindowsHandler.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
|
WindowsManager.Instance.RegisterGlobalHotkey(_config, OnHotkeyHandler, null);
|
||||||
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
MessageBus.Current.Listen<string>(EMsgCommand.AppExit.ToString()).Subscribe(StorageUI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ public partial class MainWindow
|
||||||
|
|
||||||
if (Application.Current?.MainWindow is Window window)
|
if (Application.Current?.MainWindow is Window window)
|
||||||
{
|
{
|
||||||
var bytes = QRCodeHelper.CaptureScreen(window);
|
var bytes = QRCodeUtils.CaptureScreen(window);
|
||||||
await ViewModel?.ScanScreenResult(bytes);
|
await ViewModel?.ScanScreenResult(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -430,7 +430,7 @@
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="mtu" />
|
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||||
<TextBox Style="{StaticResource DefTextBox}"
|
<TextBox Style="{StaticResource DefTextBox}"
|
||||||
x:Name="txtKcpmtu"
|
x:Name="txtKcpmtu"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
@ -1037,7 +1037,7 @@
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="Auto Route" />
|
Text="{x:Static resx:ResUI.TbSettingsTunAutoRoute}" />
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
x:Name="togAutoRoute"
|
x:Name="togAutoRoute"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
@ -1051,7 +1051,7 @@
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="Strict Route" />
|
Text="{x:Static resx:ResUI.TbSettingsTunStrictRoute}" />
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
x:Name="togStrictRoute"
|
x:Name="togStrictRoute"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
|
@ -1065,7 +1065,7 @@
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="Stack" />
|
Text="{x:Static resx:ResUI.TbSettingsTunStack}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbStack"
|
x:Name="cmbStack"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
|
@ -1081,7 +1081,7 @@
|
||||||
Margin="{StaticResource Margin8}"
|
Margin="{StaticResource Margin8}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource ToolbarTextBlock}"
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
Text="Mtu" />
|
Text="{x:Static resx:ResUI.TbSettingsTunMtu}" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="cmbMtu"
|
x:Name="cmbMtu"
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
|
|
|
@ -180,7 +180,7 @@ public partial class ProfilesView
|
||||||
|
|
||||||
public async void ShareServer(string url)
|
public async void ShareServer(string url)
|
||||||
{
|
{
|
||||||
var img = QRCodeHelper.GetQRCode(url);
|
var img = QRCodeUtils.GetQRCode(url);
|
||||||
var dialog = new QrcodeView()
|
var dialog = new QrcodeView()
|
||||||
{
|
{
|
||||||
imgQrcode = { Source = img },
|
imgQrcode = { Source = img },
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Windows.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ServiceLib.Manager;
|
using ServiceLib.Manager;
|
||||||
using Splat;
|
using Splat;
|
||||||
using v2rayN.Handler;
|
using v2rayN.Manager;
|
||||||
|
|
||||||
namespace v2rayN.Views;
|
namespace v2rayN.Views;
|
||||||
|
|
||||||
|
@ -96,8 +96,8 @@ public partial class StatusBarView
|
||||||
case EViewAction.DispatcherRefreshIcon:
|
case EViewAction.DispatcherRefreshIcon:
|
||||||
Application.Current?.Dispatcher.Invoke((async () =>
|
Application.Current?.Dispatcher.Invoke((async () =>
|
||||||
{
|
{
|
||||||
tbNotify.Icon = await WindowsHandler.Instance.GetNotifyIcon(_config);
|
tbNotify.Icon = await WindowsManager.Instance.GetNotifyIcon(_config);
|
||||||
Application.Current.MainWindow.Icon = WindowsHandler.Instance.GetAppIcon(_config);
|
Application.Current.MainWindow.Icon = WindowsManager.Instance.GetAppIcon(_config);
|
||||||
}), DispatcherPriority.Normal);
|
}), DispatcherPriority.Normal);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ public partial class SubSettingWindow
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var img = QRCodeHelper.GetQRCode(url);
|
var img = QRCodeUtils.GetQRCode(url);
|
||||||
var dialog = new QrcodeView()
|
var dialog = new QrcodeView()
|
||||||
{
|
{
|
||||||
imgQrcode = { Source = img },
|
imgQrcode = { Source = img },
|
||||||
|
|
Loading…
Reference in a new issue