mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-14 23:02:14 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f59be465ea | ||
|
|
27305317ce | ||
|
|
8378320e50 | ||
|
|
86a69952db | ||
|
|
e212ebfdb9 | ||
|
|
f235125d48 | ||
|
|
8efa815eb9 | ||
|
|
e54d42576b | ||
|
|
1e18539862 | ||
|
|
673fdf4721 | ||
|
|
57d6bfba51 | ||
|
|
8fa23aecda | ||
|
|
f9c10533e8 | ||
|
|
c891c3723e | ||
|
|
a0625ef2e7 | ||
|
|
b2774abb54 | ||
|
|
fa089bbc40 | ||
|
|
8437d1763f | ||
|
|
0532b0f9df | ||
|
|
5f8eb853f0 | ||
|
|
9125a85382 | ||
|
|
5b3dc44c72 | ||
|
|
ee5147f111 | ||
|
|
f67137fd5e | ||
|
|
b67db4a896 | ||
|
|
067018828c | ||
|
|
e990530ada | ||
|
|
45f863a72a | ||
|
|
519febbeb2 | ||
|
|
854b1a4caf | ||
|
|
08eb95e73a | ||
|
|
66f8fbf08c | ||
|
|
86a30bbb29 | ||
|
|
966c453c27 | ||
|
|
c7bcaf0193 | ||
|
|
0ac91e36a0 | ||
|
|
6f1299ce9e | ||
|
|
83ca29d649 | ||
|
|
d0206044dc | ||
|
|
d4623e4175 | ||
|
|
68ef03377d | ||
|
|
d3f9d3d033 | ||
|
|
a6ccd04471 | ||
|
|
0fd8174e77 | ||
|
|
ebc63f479a |
@@ -1,8 +1,7 @@
|
|||||||
# pnpm install --resolution-only
|
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm check $INPUT_TARGET
|
pnpm check $INPUT_TARGET
|
||||||
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
|
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
|
||||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ] || [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then
|
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||||
pnpm build --target $INPUT_TARGET
|
pnpm build --target $INPUT_TARGET
|
||||||
else
|
else
|
||||||
pnpm build --target $INPUT_TARGET -b deb
|
pnpm build --target $INPUT_TARGET -b deb
|
||||||
|
|||||||
@@ -10,15 +10,7 @@ rustup target add "$INPUT_TARGET"
|
|||||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
elif [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then
|
|
||||||
dpkg --add-architecture i386
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y libstdc++6:i386 libgdk-pixbuf2.0-dev:i386 libatomic1:i386 gcc-multilib g++-multilib libwebkit2gtk-4.0-dev:i386 libssl-dev:i386 libgtk-3-dev:i386 librsvg2-dev:i386 patchelf:i386 libayatana-appindicator3-dev:i386
|
|
||||||
export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
|
|
||||||
export PKG_CONFIG_SYSROOT_DIR=/
|
|
||||||
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
||||||
sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | tee /etc/apt/sources.list.d/ports.list
|
|
||||||
sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list
|
|
||||||
dpkg --add-architecture arm64
|
dpkg --add-architecture arm64
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
|
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
|
||||||
@@ -29,8 +21,6 @@ elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
|||||||
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
|
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
|
||||||
export PKG_CONFIG_ALLOW_CROSS=1
|
export PKG_CONFIG_ALLOW_CROSS=1
|
||||||
elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
|
elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
|
||||||
sed 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch-=amd64,i386] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | tee /etc/apt/sources.list.d/ports.list
|
|
||||||
sed -i 's/http:\/\/\(.*\).ubuntu.com\/ubuntu\//[arch=amd64,i386] http:\/\/\1.archive.ubuntu.com\/ubuntu\//g' /etc/apt/sources.list
|
|
||||||
dpkg --add-architecture armhf
|
dpkg --add-architecture armhf
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
|
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
name: Alpha Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
tags-ignore: [updater, alpha]
|
||||||
|
permissions: write-all
|
||||||
|
env:
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
|
RUST_BACKTRACE: short
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
alpha:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
- os: windows-latest
|
||||||
|
target: aarch64-pc-windows-msvc
|
||||||
|
- os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
- os: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Apply Patch
|
||||||
|
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||||
|
run: |
|
||||||
|
git config --global user.email "clash-verge-rev@github.io"
|
||||||
|
git config --global user.name "clash-verge-rev"
|
||||||
|
git am patches/support-windows-aarch64.patch
|
||||||
|
|
||||||
|
- name: Init Submodule
|
||||||
|
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Install Rust Stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Add Rust Target
|
||||||
|
run: rustup target add ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: src-tauri
|
||||||
|
|
||||||
|
- name: Install Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Pnpm install and check
|
||||||
|
run: |
|
||||||
|
pnpm i
|
||||||
|
pnpm check ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Tauri build
|
||||||
|
uses: tauri-apps/tauri-action@v0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
with:
|
||||||
|
tagName: alpha
|
||||||
|
releaseName: "Clash Verge Rev Alpha"
|
||||||
|
releaseBody: "More new features are now supported."
|
||||||
|
releaseDraft: false
|
||||||
|
prerelease: true
|
||||||
|
tauriScript: pnpm
|
||||||
|
args: --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Portable Bundle
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: pnpm portable ${{ matrix.target }} --alpha
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
|
||||||
|
alpha-for-linux:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
- os: ubuntu-latest
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build for Linux
|
||||||
|
uses: ./.github/build-for-linux
|
||||||
|
env:
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
- name: Get Version
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install jq
|
||||||
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
|
- name: Upload Release
|
||||||
|
if: startsWith(matrix.target, 'x86_64')
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: alpha
|
||||||
|
name: "Clash Verge Rev Alpha"
|
||||||
|
body: "More new features are now supported."
|
||||||
|
prerelease: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: alpha
|
||||||
|
name: "Clash Verge Rev Alpha"
|
||||||
|
body: "More new features are now supported."
|
||||||
|
prerelease: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
|
update_tag:
|
||||||
|
name: Update tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [alpha, alpha-for-linux]
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set Env
|
||||||
|
run: |
|
||||||
|
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
- name: Update Tag
|
||||||
|
uses: richardsimko/update-tag@v1
|
||||||
|
with:
|
||||||
|
tag_name: alpha
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- run: |
|
||||||
|
cat > release.txt << 'EOF'
|
||||||
|
## Clash Verge Rev Alpha
|
||||||
|
Created at ${{ env.BUILDTIME }}.
|
||||||
|
EOF
|
||||||
|
- name: Upload Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: alpha
|
||||||
|
name: "Clash Verge Rev Alpha"
|
||||||
|
body_path: release.txt
|
||||||
|
prerelease: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
generate_release_notes: true
|
||||||
@@ -2,9 +2,6 @@ name: Release Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v**
|
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
@@ -18,8 +15,6 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
- os: windows-latest
|
|
||||||
target: i686-pc-windows-msvc
|
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
target: aarch64-pc-windows-msvc
|
target: aarch64-pc-windows-msvc
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
@@ -78,7 +73,7 @@ jobs:
|
|||||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__
|
tagName: v__VERSION__
|
||||||
releaseName: "Clash Verge v__VERSION__"
|
releaseName: "Clash Verge Rev v__VERSION__"
|
||||||
releaseBody: "More new features are now supported."
|
releaseBody: "More new features are now supported."
|
||||||
releaseDraft: false
|
releaseDraft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
@@ -100,12 +95,8 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
- os: ubuntu-latest
|
|
||||||
target: i686-unknown-linux-gnu
|
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: aarch64-unknown-linux-gnu
|
target: aarch64-unknown-linux-gnu
|
||||||
# - os: ubuntu-latest
|
|
||||||
# target: armv7-unknown-linux-gnueabihf
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
@@ -124,11 +115,11 @@ jobs:
|
|||||||
sudo apt-get install jq
|
sudo apt-get install jq
|
||||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||||
- name: Upload Release
|
- name: Upload Release
|
||||||
if: startsWith(matrix.target, 'x86_64') || startsWith(matrix.target, 'i686')
|
if: startsWith(matrix.target, 'x86_64')
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: v${{env.VERSION}}
|
tag_name: v${{env.VERSION}}
|
||||||
name: "Clash Verge v${{env.VERSION}}"
|
name: "Clash Verge Rev v${{env.VERSION}}"
|
||||||
body: "More new features are now supported."
|
body: "More new features are now supported."
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||||
@@ -136,21 +127,20 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: v${{env.VERSION}}
|
tag_name: v${{env.VERSION}}
|
||||||
name: "Clash Verge v${{env.VERSION}}"
|
name: "Clash Verge Rev v${{env.VERSION}}"
|
||||||
body: "More new features are now supported."
|
body: "More new features are now supported."
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||||
|
|
||||||
release-update:
|
release-update:
|
||||||
needs: [release]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
needs: [release, release-for-linux]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,24 @@
|
|||||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Click on the corresponding link below to download the installation package. Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||||
|
|
||||||
|
[[Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_x64-setup.exe)]
|
||||||
|
[[Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_arm64-setup.exe)]
|
||||||
|
|
||||||
|
[[macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_x64.dmg)]
|
||||||
|
[[macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/Clash.Verge_1.5.2_aarch64.dmg)]
|
||||||
|
|
||||||
|
[[Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/clash-verge_1.5.2_amd64.AppImage)]
|
||||||
|
[[Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/clash-verge_1.5.2_amd64.deb)]
|
||||||
|
[[Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.2/clash-verge_1.5.2_arm64.deb)]
|
||||||
|
|
||||||
|
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
||||||
|
|
||||||
|
Notes: If you could not start the app on Windows, please check that you have [Webview2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) installed.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||||
@@ -37,27 +55,6 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Download from [release](https://github.com/clash-verge-rev/clash-verge-rev/releases). Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
|
||||||
|
|
||||||
- [Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/Clash.Verge_1.4.10_x64-setup.exe)
|
|
||||||
- [Windows x86](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/Clash.Verge_1.4.10_x86-setup.exe)
|
|
||||||
- [Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/Clash.Verge_1.4.10_arm64-setup.exe)
|
|
||||||
|
|
||||||
- [macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/Clash.Verge_1.4.10_x64.dmg)
|
|
||||||
- [macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/Clash.Verge_1.4.10_aarch64.dmg)
|
|
||||||
|
|
||||||
- [Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/clash-verge_1.4.10_amd64.AppImage)
|
|
||||||
- [Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/clash-verge_1.4.10_amd64.deb)
|
|
||||||
- [Linux x86 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/clash-verge_1.4.10_i386.AppImage)
|
|
||||||
- [Linux x86 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/clash-verge_1.4.10_i386.deb)
|
|
||||||
- [Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.10/clash-verge_1.4.10_arm64.deb)
|
|
||||||
|
|
||||||
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
|
||||||
|
|
||||||
Notes: If you could not start the app on Windows, please check that you have [Webview2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) installed.
|
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
#### 1. **macOS** "Clash Verge" is damaged and can't be opened
|
#### 1. **macOS** "Clash Verge" is damaged and can't be opened
|
||||||
|
|||||||
+65
-1
@@ -1,3 +1,67 @@
|
|||||||
|
## v1.5.2
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- 支持自定义延迟测试超时时间
|
||||||
|
- 优化 Tun 相关设置
|
||||||
|
|
||||||
|
### Bugs Fixes
|
||||||
|
|
||||||
|
- Merge 操作出错
|
||||||
|
- 安装后重启服务
|
||||||
|
- 修复管理员权限启动时开机启动失效的问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.5.1
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- 保存窗口最大化状态
|
||||||
|
- Proxy Provider 显示数量
|
||||||
|
- 不再提供 32 位安装包(因为 32 位经常出现各种奇怪问题,比如 tun 模式无法开启;现在系统也几乎没有 32 位了)
|
||||||
|
|
||||||
|
### Bugs Fixes
|
||||||
|
|
||||||
|
- 优化设置项名称
|
||||||
|
- 自定义 GLOBAL 代理组时代理组显示错误的问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.5.0
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- 删除 Clash 字段过滤功能
|
||||||
|
- 添加 socks 端口和 http 端口设置
|
||||||
|
- 升级内核到 1.18.1
|
||||||
|
|
||||||
|
### Bugs Fixes
|
||||||
|
|
||||||
|
- 修复 32 位版本无法显示流量信息的问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1.4.11
|
||||||
|
|
||||||
|
### Break Changes
|
||||||
|
|
||||||
|
- 此版本更改了 Windows 安装包安装模式,需要卸载后手动安装,否则无法安装到正确位置
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- 优化了系统代理开启的代码,解决了稀有场景下代理开启卡顿的问题
|
||||||
|
- 添加 MacOS 下的 debug 日志,以便日后调试稀有场景下 MacOS 下无法开启系统代理的问题
|
||||||
|
- MacOS 关闭 GUI 时同步杀除后台 GUI [#306](https://github.com/clash-verge-rev/clash-verge-rev/issues/306)
|
||||||
|
|
||||||
|
### Bugs Fixes
|
||||||
|
|
||||||
|
- 解决自动更新时文件占用问题
|
||||||
|
- 解决稀有场景下系统代理开启失败的问题
|
||||||
|
- 删除冗余内核代码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v1.4.10
|
## v1.4.10
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@@ -7,7 +71,7 @@
|
|||||||
- 在 Proxy Provider 页面展示订阅信息
|
- 在 Proxy Provider 页面展示订阅信息
|
||||||
- 优化 Provider 支持
|
- 优化 Provider 支持
|
||||||
|
|
||||||
### Bugs fixed:
|
### Bugs Fixes
|
||||||
|
|
||||||
- 更改端口时立即重设系统代理
|
- 更改端口时立即重设系统代理
|
||||||
- 网站测试超时错误
|
- 网站测试超时错误
|
||||||
|
|||||||
+2
-3
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "clash-verge",
|
||||||
"version": "1.4.10",
|
"version": "1.5.2",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tauri dev",
|
"dev": "tauri dev",
|
||||||
"dev:diff": "tauri dev -f verge-dev",
|
"dev:diff": "tauri dev -f verge-dev",
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
"@tauri-apps/cli": "^1.5.9",
|
"@tauri-apps/cli": "^1.5.9",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/lodash": "^4.14.202",
|
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.2.48",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.18",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
From 8b085aea2f11e64f433244eda092c178a2bb50bc Mon Sep 17 00:00:00 2001
|
From 871c9a6d1ed014c93da2436a437df03734e9f76c Mon Sep 17 00:00:00 2001
|
||||||
From: MystiPanda <mystipanda@proton.me>
|
From: MystiPanda <mystipanda@proton.me>
|
||||||
Date: Sun, 10 Dec 2023 19:47:45 +0800
|
Date: Sun, 10 Dec 2023 19:47:45 +0800
|
||||||
Subject: [PATCH] feat: Support windows aarch64
|
Subject: [PATCH] feat: Support windows aarch64
|
||||||
@@ -22,7 +22,7 @@ index 0000000..2eda7e4
|
|||||||
+ path = src-tauri/quick-rs
|
+ path = src-tauri/quick-rs
|
||||||
+ url = https://github.com/clash-verge-rev/quick-rs.git
|
+ url = https://github.com/clash-verge-rev/quick-rs.git
|
||||||
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
|
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
|
||||||
index 4c6dde5..5fd9ad8 100644
|
index 2f1a3be..d67f6ed 100644
|
||||||
--- a/src-tauri/Cargo.toml
|
--- a/src-tauri/Cargo.toml
|
||||||
+++ b/src-tauri/Cargo.toml
|
+++ b/src-tauri/Cargo.toml
|
||||||
@@ -25,7 +25,6 @@ log4rs = "1"
|
@@ -25,7 +25,6 @@ log4rs = "1"
|
||||||
@@ -32,8 +32,8 @@ index 4c6dde5..5fd9ad8 100644
|
|||||||
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
auto-launch = "0.5"
|
once_cell = "1.18"
|
||||||
@@ -34,6 +33,7 @@ port_scanner = "0.1.5"
|
@@ -33,6 +32,7 @@ port_scanner = "0.1.5"
|
||||||
delay_timer = "0.11.5"
|
delay_timer = "0.11.5"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
@@ -49,7 +49,7 @@ index 0000000..78277c4
|
|||||||
@@ -0,0 +1 @@
|
@@ -0,0 +1 @@
|
||||||
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
||||||
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
||||||
index 6c207d9..d47dc33 100644
|
index 30a922f..d47dc33 100644
|
||||||
--- a/src-tauri/src/enhance/script.rs
|
--- a/src-tauri/src/enhance/script.rs
|
||||||
+++ b/src-tauri/src/enhance/script.rs
|
+++ b/src-tauri/src/enhance/script.rs
|
||||||
@@ -3,61 +3,83 @@ use anyhow::Result;
|
@@ -3,61 +3,83 @@ use anyhow::Result;
|
||||||
|
|||||||
Generated
-3
@@ -109,9 +109,6 @@ devDependencies:
|
|||||||
"@types/js-cookie":
|
"@types/js-cookie":
|
||||||
specifier: ^3.0.6
|
specifier: ^3.0.6
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
"@types/lodash":
|
|
||||||
specifier: ^4.14.202
|
|
||||||
version: 4.14.202
|
|
||||||
"@types/lodash-es":
|
"@types/lodash-es":
|
||||||
specifier: ^4.17.12
|
specifier: ^4.17.12
|
||||||
version: 4.17.12
|
version: 4.17.12
|
||||||
|
|||||||
+33
-8
@@ -13,24 +13,20 @@ const FORCE = process.argv.includes("--force");
|
|||||||
|
|
||||||
const PLATFORM_MAP = {
|
const PLATFORM_MAP = {
|
||||||
"x86_64-pc-windows-msvc": "win32",
|
"x86_64-pc-windows-msvc": "win32",
|
||||||
"i686-pc-windows-msvc": "win32",
|
|
||||||
"aarch64-pc-windows-msvc": "win32",
|
"aarch64-pc-windows-msvc": "win32",
|
||||||
"x86_64-apple-darwin": "darwin",
|
"x86_64-apple-darwin": "darwin",
|
||||||
"aarch64-apple-darwin": "darwin",
|
"aarch64-apple-darwin": "darwin",
|
||||||
"x86_64-unknown-linux-gnu": "linux",
|
"x86_64-unknown-linux-gnu": "linux",
|
||||||
"i686-unknown-linux-gnu": "linux",
|
|
||||||
"aarch64-unknown-linux-gnu": "linux",
|
"aarch64-unknown-linux-gnu": "linux",
|
||||||
"armv7-unknown-linux-gnueabihf": "linux",
|
"armv7-unknown-linux-gnueabihf": "linux",
|
||||||
"loongarch64-unknown-linux-gnu": "linux",
|
"loongarch64-unknown-linux-gnu": "linux",
|
||||||
};
|
};
|
||||||
const ARCH_MAP = {
|
const ARCH_MAP = {
|
||||||
"x86_64-pc-windows-msvc": "x64",
|
"x86_64-pc-windows-msvc": "x64",
|
||||||
"i686-pc-windows-msvc": "ia32",
|
|
||||||
"aarch64-pc-windows-msvc": "arm64",
|
"aarch64-pc-windows-msvc": "arm64",
|
||||||
"x86_64-apple-darwin": "x64",
|
"x86_64-apple-darwin": "x64",
|
||||||
"aarch64-apple-darwin": "arm64",
|
"aarch64-apple-darwin": "arm64",
|
||||||
"x86_64-unknown-linux-gnu": "x64",
|
"x86_64-unknown-linux-gnu": "x64",
|
||||||
"i686-unknown-linux-gnu": "ia32",
|
|
||||||
"aarch64-unknown-linux-gnu": "arm64",
|
"aarch64-unknown-linux-gnu": "arm64",
|
||||||
"armv7-unknown-linux-gnueabihf": "arm",
|
"armv7-unknown-linux-gnueabihf": "arm",
|
||||||
"loongarch64-unknown-linux-gnu": "loong64",
|
"loongarch64-unknown-linux-gnu": "loong64",
|
||||||
@@ -57,12 +53,10 @@ let META_ALPHA_VERSION;
|
|||||||
|
|
||||||
const META_ALPHA_MAP = {
|
const META_ALPHA_MAP = {
|
||||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||||
"win32-ia32": "mihomo-windows-386",
|
|
||||||
"win32-arm64": "mihomo-windows-arm64",
|
"win32-arm64": "mihomo-windows-arm64",
|
||||||
"darwin-x64": "mihomo-darwin-amd64",
|
"darwin-x64": "mihomo-darwin-amd64",
|
||||||
"darwin-arm64": "mihomo-darwin-arm64",
|
"darwin-arm64": "mihomo-darwin-arm64",
|
||||||
"linux-x64": "mihomo-linux-amd64-compatible",
|
"linux-x64": "mihomo-linux-amd64-compatible",
|
||||||
"linux-ia32": "mihomo-linux-386",
|
|
||||||
"linux-arm64": "mihomo-linux-arm64",
|
"linux-arm64": "mihomo-linux-arm64",
|
||||||
"linux-arm": "mihomo-linux-armv7",
|
"linux-arm": "mihomo-linux-armv7",
|
||||||
"linux-loong64": "mihomo-linux-loong64",
|
"linux-loong64": "mihomo-linux-loong64",
|
||||||
@@ -103,12 +97,10 @@ let META_VERSION;
|
|||||||
|
|
||||||
const META_MAP = {
|
const META_MAP = {
|
||||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||||
"win32-ia32": "mihomo-windows-386",
|
|
||||||
"win32-arm64": "mihomo-windows-arm64",
|
"win32-arm64": "mihomo-windows-arm64",
|
||||||
"darwin-x64": "mihomo-darwin-amd64",
|
"darwin-x64": "mihomo-darwin-amd64",
|
||||||
"darwin-arm64": "mihomo-darwin-arm64",
|
"darwin-arm64": "mihomo-darwin-arm64",
|
||||||
"linux-x64": "mihomo-linux-amd64-compatible",
|
"linux-x64": "mihomo-linux-amd64-compatible",
|
||||||
"linux-ia32": "mihomo-linux-386",
|
|
||||||
"linux-arm64": "mihomo-linux-arm64",
|
"linux-arm64": "mihomo-linux-arm64",
|
||||||
"linux-arm": "mihomo-linux-armv7",
|
"linux-arm": "mihomo-linux-armv7",
|
||||||
"linux-loong64": "mihomo-linux-loong64",
|
"linux-loong64": "mihomo-linux-loong64",
|
||||||
@@ -317,6 +309,38 @@ async function downloadFile(url, path) {
|
|||||||
console.log(`[INFO]: download finished "${url}"`);
|
console.log(`[INFO]: download finished "${url}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimpleSC.dll
|
||||||
|
const resolvePlugin = async () => {
|
||||||
|
const url =
|
||||||
|
"https://nsis.sourceforge.io/mediawiki/images/e/ef/NSIS_Simple_Service_Plugin_Unicode_1.30.zip";
|
||||||
|
|
||||||
|
const tempDir = path.join(TEMP_DIR, "SimpleSC");
|
||||||
|
const tempZip = path.join(
|
||||||
|
tempDir,
|
||||||
|
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
|
||||||
|
);
|
||||||
|
const tempDll = path.join(tempDir, "SimpleSC.dll");
|
||||||
|
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
|
||||||
|
const pluginPath = path.join(pluginDir, "SimpleSC.dll");
|
||||||
|
await fs.mkdirp(pluginDir);
|
||||||
|
await fs.mkdirp(tempDir);
|
||||||
|
if (!FORCE && (await fs.pathExists(pluginPath))) return;
|
||||||
|
try {
|
||||||
|
if (!(await fs.pathExists(tempZip))) {
|
||||||
|
await downloadFile(url, tempZip);
|
||||||
|
}
|
||||||
|
const zip = new AdmZip(tempZip);
|
||||||
|
zip.getEntries().forEach((entry) => {
|
||||||
|
console.log(`[DEBUG]: "SimpleSC" entry name`, entry.entryName);
|
||||||
|
});
|
||||||
|
zip.extractAllTo(tempDir, true);
|
||||||
|
await fs.copyFile(tempDll, pluginPath);
|
||||||
|
console.log(`[INFO]: "SimpleSC" unzip finished`);
|
||||||
|
} finally {
|
||||||
|
await fs.remove(tempDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main
|
* main
|
||||||
*/
|
*/
|
||||||
@@ -373,6 +397,7 @@ const tasks = [
|
|||||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||||
retry: 5,
|
retry: 5,
|
||||||
},
|
},
|
||||||
|
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
|
||||||
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||||
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||||
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
||||||
|
|||||||
+15
-4
@@ -5,9 +5,9 @@ import { createRequire } from "module";
|
|||||||
import { getOctokit, context } from "@actions/github";
|
import { getOctokit, context } from "@actions/github";
|
||||||
|
|
||||||
const target = process.argv.slice(2)[0];
|
const target = process.argv.slice(2)[0];
|
||||||
|
const alpha = process.argv.slice(2)[1];
|
||||||
|
|
||||||
const ARCH_MAP = {
|
const ARCH_MAP = {
|
||||||
"i686-pc-windows-msvc": "x86",
|
|
||||||
"x86_64-pc-windows-msvc": "x64",
|
"x86_64-pc-windows-msvc": "x64",
|
||||||
"aarch64-pc-windows-msvc": "arm64",
|
"aarch64-pc-windows-msvc": "arm64",
|
||||||
};
|
};
|
||||||
@@ -53,14 +53,25 @@ async function resolvePortable() {
|
|||||||
|
|
||||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||||
|
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
|
||||||
console.log("[INFO]: upload to ", process.env.TAG_NAME || `v${version}`);
|
console.log("[INFO]: upload to ", tag);
|
||||||
|
|
||||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||||
...options,
|
...options,
|
||||||
tag: process.env.TAG_NAME || `v${version}`,
|
tag,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let assets = release.assets.filter((x) => {
|
||||||
|
return x.name === zipFile;
|
||||||
|
});
|
||||||
|
if (assets.length > 0) {
|
||||||
|
let id = assets[0].id;
|
||||||
|
await github.rest.repos.deleteReleaseAsset({
|
||||||
|
...options,
|
||||||
|
asset_id: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log(release.name);
|
console.log(release.name);
|
||||||
|
|
||||||
await github.rest.repos.uploadReleaseAsset({
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
|||||||
@@ -45,11 +45,9 @@ async function resolveUpdater() {
|
|||||||
"darwin-intel": { signature: "", url: "" },
|
"darwin-intel": { signature: "", url: "" },
|
||||||
"darwin-x86_64": { signature: "", url: "" },
|
"darwin-x86_64": { signature: "", url: "" },
|
||||||
"linux-x86_64": { signature: "", url: "" },
|
"linux-x86_64": { signature: "", url: "" },
|
||||||
"linux-i686": { signature: "", url: "" },
|
|
||||||
"linux-aarch64": { signature: "", url: "" },
|
"linux-aarch64": { signature: "", url: "" },
|
||||||
"linux-armv7": { signature: "", url: "" },
|
"linux-armv7": { signature: "", url: "" },
|
||||||
"windows-x86_64": { signature: "", url: "" },
|
"windows-x86_64": { signature: "", url: "" },
|
||||||
"windows-i686": { signature: "", url: "" },
|
|
||||||
"windows-aarch64": { signature: "", url: "" },
|
"windows-aarch64": { signature: "", url: "" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -69,16 +67,6 @@ async function resolveUpdater() {
|
|||||||
updateData.platforms["windows-x86_64"].signature = sig;
|
updateData.platforms["windows-x86_64"].signature = sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// win32 url
|
|
||||||
if (name.endsWith("x86-setup.nsis.zip")) {
|
|
||||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// win32 signature
|
|
||||||
if (name.endsWith("x86-setup.nsis.zip.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["windows-i686"].signature = sig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// win arm url
|
// win arm url
|
||||||
if (name.endsWith("arm64-setup.nsis.zip")) {
|
if (name.endsWith("arm64-setup.nsis.zip")) {
|
||||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
||||||
@@ -130,16 +118,6 @@ async function resolveUpdater() {
|
|||||||
updateData.platforms["linux-aarch64"].signature = sig;
|
updateData.platforms["linux-aarch64"].signature = sig;
|
||||||
updateData.platforms["linux-armv7"].signature = sig;
|
updateData.platforms["linux-armv7"].signature = sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// linux x86 url
|
|
||||||
if (name.endsWith("i386.AppImage.tar.gz")) {
|
|
||||||
updateData.platforms["linux-i686"].url = browser_download_url;
|
|
||||||
}
|
|
||||||
// linux x86 signature
|
|
||||||
if (name.endsWith("i386.AppImage.tar.gz.sig")) {
|
|
||||||
const sig = await getSignature(browser_download_url);
|
|
||||||
updateData.platforms["linux-i686"].signature = sig;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
await Promise.allSettled(promises);
|
||||||
|
|||||||
Generated
+204
-193
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "clash-verge"
|
name = "clash-verge"
|
||||||
version = "1.4.10"
|
version = "1.5.2"
|
||||||
description = "clash verge"
|
description = "clash verge"
|
||||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
||||||
default-run = "clash-verge"
|
default-run = "clash-verge"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -14,7 +14,7 @@ tauri-build = { version = "1", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
warp = "0.3"
|
warp = "0.3"
|
||||||
which = "5.0.0"
|
which = "6.0.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
open = "5.0"
|
open = "5.0"
|
||||||
@@ -28,7 +28,6 @@ sysinfo = "0.30"
|
|||||||
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
auto-launch = "0.5"
|
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
port_scanner = "0.1.5"
|
port_scanner = "0.1.5"
|
||||||
delay_timer = "0.11.5"
|
delay_timer = "0.11.5"
|
||||||
@@ -38,7 +37,8 @@ window-shadows = { version = "0.2" }
|
|||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||||
sysproxy = { git="https://github.com/clash-verge-rev/sysproxy-rs", branch = "main" }
|
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
|
||||||
|
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
|
||||||
tauri = { version = "1.5", features = [ "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
tauri = { version = "1.5", features = [ "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
|||||||
+13
-3
@@ -2,14 +2,14 @@ use crate::{
|
|||||||
config::*,
|
config::*,
|
||||||
core::*,
|
core::*,
|
||||||
feat,
|
feat,
|
||||||
utils::{dirs, help},
|
utils::{dirs, help, resolve},
|
||||||
};
|
};
|
||||||
use crate::{ret_err, wrap_err};
|
use crate::{ret_err, wrap_err};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use sysproxy::Sysproxy;
|
use sysproxy::Sysproxy;
|
||||||
|
use tauri::api;
|
||||||
type CmdResult<T = ()> = Result<T, String>;
|
type CmdResult<T = ()> = Result<T, String>;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -249,8 +249,9 @@ pub mod uwp {
|
|||||||
pub async fn clash_api_get_proxy_delay(
|
pub async fn clash_api_get_proxy_delay(
|
||||||
name: String,
|
name: String,
|
||||||
url: Option<String>,
|
url: Option<String>,
|
||||||
|
timeout: i32,
|
||||||
) -> CmdResult<clash_api::DelayRes> {
|
) -> CmdResult<clash_api::DelayRes> {
|
||||||
match clash_api::get_proxy_delay(name, url).await {
|
match clash_api::get_proxy_delay(name, url, timeout).await {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
@@ -266,6 +267,15 @@ pub async fn test_delay(url: String) -> CmdResult<u32> {
|
|||||||
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
|
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||||
|
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||||
|
resolve::resolve_reset();
|
||||||
|
api::process::kill_children();
|
||||||
|
app_handle.exit(0);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod service {
|
pub mod service {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -12,33 +12,56 @@ pub struct IClashTemp(pub Mapping);
|
|||||||
|
|
||||||
impl IClashTemp {
|
impl IClashTemp {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let template = Self::template();
|
||||||
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
|
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
|
||||||
Ok(map) => Self(Self::guard(map)),
|
Ok(mut map) => {
|
||||||
|
template.0.keys().for_each(|key| {
|
||||||
|
if !map.contains_key(key) {
|
||||||
|
map.insert(key.clone(), template.0.get(key).unwrap().clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self(Self::guard(map))
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!(target: "app", "{err}");
|
log::error!(target: "app", "{err}");
|
||||||
Self::template()
|
template
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
|
let mut tun = Mapping::new();
|
||||||
|
tun.insert("stack".into(), "gVisor".into());
|
||||||
|
tun.insert("device".into(), "Meta".into());
|
||||||
|
tun.insert("auto-route".into(), true.into());
|
||||||
|
tun.insert("strict-route".into(), true.into());
|
||||||
|
tun.insert("auto-detect-interface".into(), true.into());
|
||||||
|
tun.insert("dns-hijack".into(), vec!["any:53", "tcp://any:53"].into());
|
||||||
|
tun.insert("mtu".into(), 9000.into());
|
||||||
|
|
||||||
map.insert("mixed-port".into(), 7897.into());
|
map.insert("mixed-port".into(), 7897.into());
|
||||||
|
map.insert("socks-port".into(), 7898.into());
|
||||||
|
map.insert("port".into(), 7899.into());
|
||||||
map.insert("log-level".into(), "info".into());
|
map.insert("log-level".into(), "info".into());
|
||||||
map.insert("allow-lan".into(), false.into());
|
map.insert("allow-lan".into(), false.into());
|
||||||
map.insert("mode".into(), "rule".into());
|
map.insert("mode".into(), "rule".into());
|
||||||
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||||
map.insert("secret".into(), "".into());
|
map.insert("secret".into(), "".into());
|
||||||
|
map.insert("tun".into(), tun.into());
|
||||||
|
|
||||||
Self(map)
|
Self(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guard(mut config: Mapping) -> Mapping {
|
fn guard(mut config: Mapping) -> Mapping {
|
||||||
let port = Self::guard_mixed_port(&config);
|
let mixed_port = Self::guard_mixed_port(&config);
|
||||||
|
let socks_port = Self::guard_socks_port(&config);
|
||||||
|
let port = Self::guard_port(&config);
|
||||||
let ctrl = Self::guard_server_ctrl(&config);
|
let ctrl = Self::guard_server_ctrl(&config);
|
||||||
|
|
||||||
config.insert("mixed-port".into(), port.into());
|
config.insert("mixed-port".into(), mixed_port.into());
|
||||||
|
config.insert("socks-port".into(), socks_port.into());
|
||||||
|
config.insert("port".into(), port.into());
|
||||||
config.insert("external-controller".into(), ctrl.into());
|
config.insert("external-controller".into(), ctrl.into());
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
@@ -61,11 +84,23 @@ impl IClashTemp {
|
|||||||
Self::guard_mixed_port(&self.0)
|
Self::guard_mixed_port(&self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn get_socks_port(&self) -> u16 {
|
||||||
|
Self::guard_socks_port(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn get_port(&self) -> u16 {
|
||||||
|
Self::guard_port(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_client_info(&self) -> ClashInfo {
|
pub fn get_client_info(&self) -> ClashInfo {
|
||||||
let config = &self.0;
|
let config = &self.0;
|
||||||
|
|
||||||
ClashInfo {
|
ClashInfo {
|
||||||
port: Self::guard_mixed_port(config),
|
mixed_port: Self::guard_mixed_port(config),
|
||||||
|
socks_port: Self::guard_socks_port(config),
|
||||||
|
port: Self::guard_port(config),
|
||||||
server: Self::guard_client_ctrl(config),
|
server: Self::guard_client_ctrl(config),
|
||||||
secret: config.get("secret").and_then(|value| match value {
|
secret: config.get("secret").and_then(|value| match value {
|
||||||
Value::String(val_str) => Some(val_str.clone()),
|
Value::String(val_str) => Some(val_str.clone()),
|
||||||
@@ -91,6 +126,36 @@ impl IClashTemp {
|
|||||||
port
|
port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn guard_socks_port(config: &Mapping) -> u16 {
|
||||||
|
let mut port = config
|
||||||
|
.get("socks-port")
|
||||||
|
.and_then(|value| match value {
|
||||||
|
Value::String(val_str) => val_str.parse().ok(),
|
||||||
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(7898);
|
||||||
|
if port == 0 {
|
||||||
|
port = 7898;
|
||||||
|
}
|
||||||
|
port
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guard_port(config: &Mapping) -> u16 {
|
||||||
|
let mut port = config
|
||||||
|
.get("port")
|
||||||
|
.and_then(|value| match value {
|
||||||
|
Value::String(val_str) => val_str.parse().ok(),
|
||||||
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(7899);
|
||||||
|
if port == 0 {
|
||||||
|
port = 7899;
|
||||||
|
}
|
||||||
|
port
|
||||||
|
}
|
||||||
|
|
||||||
pub fn guard_server_ctrl(config: &Mapping) -> String {
|
pub fn guard_server_ctrl(config: &Mapping) -> String {
|
||||||
config
|
config
|
||||||
.get("external-controller")
|
.get("external-controller")
|
||||||
@@ -129,6 +194,8 @@ impl IClashTemp {
|
|||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct ClashInfo {
|
pub struct ClashInfo {
|
||||||
/// clash core port
|
/// clash core port
|
||||||
|
pub mixed_port: u16,
|
||||||
|
pub socks_port: u16,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
/// same as `external-controller`
|
/// same as `external-controller`
|
||||||
pub server: String,
|
pub server: String,
|
||||||
@@ -148,7 +215,9 @@ fn test_clash_info() {
|
|||||||
|
|
||||||
fn get_result<S: Into<String>>(port: u16, server: S) -> ClashInfo {
|
fn get_result<S: Into<String>>(port: u16, server: S) -> ClashInfo {
|
||||||
ClashInfo {
|
ClashInfo {
|
||||||
port,
|
mixed_port: port,
|
||||||
|
socks_port: 7898,
|
||||||
|
port: 7899,
|
||||||
server: server.into(),
|
server: server.into(),
|
||||||
secret: None,
|
secret: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ pub struct PrfSelected {
|
|||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||||
pub struct PrfExtra {
|
pub struct PrfExtra {
|
||||||
pub upload: usize,
|
pub upload: u64,
|
||||||
pub download: usize,
|
pub download: u64,
|
||||||
pub total: usize,
|
pub total: u64,
|
||||||
pub expire: usize,
|
pub expire: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ pub struct IProfiles {
|
|||||||
/// same as PrfConfig.chain
|
/// same as PrfConfig.chain
|
||||||
pub chain: Option<Vec<String>>,
|
pub chain: Option<Vec<String>>,
|
||||||
|
|
||||||
/// record valid fields for clash
|
|
||||||
pub valid: Option<Vec<String>>,
|
|
||||||
|
|
||||||
/// profile list
|
/// profile list
|
||||||
pub items: Option<Vec<PrfItem>>,
|
pub items: Option<Vec<PrfItem>>,
|
||||||
}
|
}
|
||||||
@@ -55,12 +52,6 @@ impl IProfiles {
|
|||||||
|
|
||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
Self {
|
Self {
|
||||||
valid: Some(vec![
|
|
||||||
"dns".into(),
|
|
||||||
"sub-rules".into(),
|
|
||||||
"unified-delay".into(),
|
|
||||||
"tcp-concurrent".into(),
|
|
||||||
]),
|
|
||||||
items: Some(vec![]),
|
items: Some(vec![]),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
@@ -93,10 +84,6 @@ impl IProfiles {
|
|||||||
self.chain = Some(chain);
|
self.chain = Some(chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(valid) = patch.valid {
|
|
||||||
self.valid = Some(valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use crate::enhance::field::use_keys;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::{Mapping, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct IRuntime {
|
pub struct IRuntime {
|
||||||
pub config: Option<Mapping>,
|
pub config: Option<Mapping>,
|
||||||
@@ -16,7 +16,7 @@ impl IRuntime {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这里只更改 allow-lan | ipv6 | log-level
|
// 这里只更改 allow-lan | ipv6 | log-level | tun
|
||||||
pub fn patch_config(&mut self, patch: Mapping) {
|
pub fn patch_config(&mut self, patch: Mapping) {
|
||||||
if let Some(config) = self.config.as_mut() {
|
if let Some(config) = self.config.as_mut() {
|
||||||
["allow-lan", "ipv6", "log-level"]
|
["allow-lan", "ipv6", "log-level"]
|
||||||
@@ -26,6 +26,20 @@ impl IRuntime {
|
|||||||
config.insert(key.into(), value.clone());
|
config.insert(key.into(), value.clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let tun = config.get("tun");
|
||||||
|
let mut tun = tun.map_or(Mapping::new(), |val| {
|
||||||
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
|
});
|
||||||
|
let patch_tun = patch.get("tun");
|
||||||
|
let patch_tun = patch_tun.map_or(Mapping::new(), |val| {
|
||||||
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
|
});
|
||||||
|
use_keys(&patch_tun).into_iter().for_each(|key| {
|
||||||
|
if let Some(value) = patch_tun.get(&key).to_owned() {
|
||||||
|
tun.insert(key.into(), value.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
config.insert("tun".into(), Value::from(tun));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ pub struct IVerge {
|
|||||||
/// set system proxy bypass
|
/// set system proxy bypass
|
||||||
pub system_proxy_bypass: Option<String>,
|
pub system_proxy_bypass: Option<String>,
|
||||||
|
|
||||||
/// set system proxy method
|
|
||||||
pub system_proxy_registry_mode: Option<bool>,
|
|
||||||
|
|
||||||
/// proxy guard duration
|
/// proxy guard duration
|
||||||
pub proxy_guard_duration: Option<u64>,
|
pub proxy_guard_duration: Option<u64>,
|
||||||
|
|
||||||
@@ -84,8 +81,8 @@ pub struct IVerge {
|
|||||||
/// 默认的延迟测试连接
|
/// 默认的延迟测试连接
|
||||||
pub default_latency_test: Option<String>,
|
pub default_latency_test: Option<String>,
|
||||||
|
|
||||||
/// 支持关闭字段过滤,避免meta的新字段都被过滤掉,默认为关闭
|
/// 默认的延迟测试超时时间
|
||||||
pub enable_clash_fields: Option<bool>,
|
pub default_latency_timeout: Option<i32>,
|
||||||
|
|
||||||
/// 是否使用内部的脚本支持,默认为真
|
/// 是否使用内部的脚本支持,默认为真
|
||||||
pub enable_builtin_enhanced: Option<bool>,
|
pub enable_builtin_enhanced: Option<bool>,
|
||||||
@@ -104,11 +101,19 @@ pub struct IVerge {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub window_size_position: Option<Vec<f64>>,
|
pub window_size_position: Option<Vec<f64>>,
|
||||||
|
|
||||||
|
/// window size and position
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub window_is_maximized: Option<bool>,
|
||||||
|
|
||||||
/// 是否启用随机端口
|
/// 是否启用随机端口
|
||||||
pub enable_random_port: Option<bool>,
|
pub enable_random_port: Option<bool>,
|
||||||
|
|
||||||
/// verge mixed port 用于覆盖 clash 的 mixed port
|
/// verge mixed port 用于覆盖 clash 的 mixed port
|
||||||
pub verge_mixed_port: Option<u16>,
|
pub verge_mixed_port: Option<u16>,
|
||||||
|
|
||||||
|
pub verge_socks_port: Option<u16>,
|
||||||
|
|
||||||
|
pub verge_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
@@ -161,14 +166,14 @@ impl IVerge {
|
|||||||
enable_auto_launch: Some(false),
|
enable_auto_launch: Some(false),
|
||||||
enable_silent_start: Some(false),
|
enable_silent_start: Some(false),
|
||||||
enable_system_proxy: Some(false),
|
enable_system_proxy: Some(false),
|
||||||
system_proxy_registry_mode: Some(false),
|
|
||||||
enable_random_port: Some(false),
|
enable_random_port: Some(false),
|
||||||
verge_mixed_port: Some(7897),
|
verge_mixed_port: Some(7897),
|
||||||
|
verge_socks_port: Some(7898),
|
||||||
|
verge_port: Some(7899),
|
||||||
enable_proxy_guard: Some(false),
|
enable_proxy_guard: Some(false),
|
||||||
proxy_guard_duration: Some(30),
|
proxy_guard_duration: Some(30),
|
||||||
auto_close_connection: Some(true),
|
auto_close_connection: Some(true),
|
||||||
enable_builtin_enhanced: Some(true),
|
enable_builtin_enhanced: Some(true),
|
||||||
enable_clash_fields: Some(true),
|
|
||||||
auto_log_clean: Some(3),
|
auto_log_clean: Some(3),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
@@ -206,10 +211,11 @@ impl IVerge {
|
|||||||
patch!(enable_silent_start);
|
patch!(enable_silent_start);
|
||||||
patch!(enable_random_port);
|
patch!(enable_random_port);
|
||||||
patch!(verge_mixed_port);
|
patch!(verge_mixed_port);
|
||||||
|
patch!(verge_socks_port);
|
||||||
|
patch!(verge_port);
|
||||||
patch!(enable_system_proxy);
|
patch!(enable_system_proxy);
|
||||||
patch!(enable_proxy_guard);
|
patch!(enable_proxy_guard);
|
||||||
patch!(system_proxy_bypass);
|
patch!(system_proxy_bypass);
|
||||||
patch!(system_proxy_registry_mode);
|
|
||||||
patch!(proxy_guard_duration);
|
patch!(proxy_guard_duration);
|
||||||
|
|
||||||
patch!(theme_setting);
|
patch!(theme_setting);
|
||||||
@@ -219,12 +225,13 @@ impl IVerge {
|
|||||||
|
|
||||||
patch!(auto_close_connection);
|
patch!(auto_close_connection);
|
||||||
patch!(default_latency_test);
|
patch!(default_latency_test);
|
||||||
|
patch!(default_latency_timeout);
|
||||||
patch!(enable_builtin_enhanced);
|
patch!(enable_builtin_enhanced);
|
||||||
patch!(proxy_layout_column);
|
patch!(proxy_layout_column);
|
||||||
patch!(test_list);
|
patch!(test_list);
|
||||||
patch!(enable_clash_fields);
|
|
||||||
patch!(auto_log_clean);
|
patch!(auto_log_clean);
|
||||||
patch!(window_size_position);
|
patch!(window_size_position);
|
||||||
|
patch!(window_is_maximized);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 在初始化前尝试拿到单例端口的值
|
/// 在初始化前尝试拿到单例端口的值
|
||||||
|
|||||||
@@ -44,7 +44,11 @@ pub struct DelayRes {
|
|||||||
|
|
||||||
/// GET /proxies/{name}/delay
|
/// GET /proxies/{name}/delay
|
||||||
/// 获取代理延迟
|
/// 获取代理延迟
|
||||||
pub async fn get_proxy_delay(name: String, test_url: Option<String>) -> Result<DelayRes> {
|
pub async fn get_proxy_delay(
|
||||||
|
name: String,
|
||||||
|
test_url: Option<String>,
|
||||||
|
timeout: i32,
|
||||||
|
) -> Result<DelayRes> {
|
||||||
let (url, headers) = clash_client_info()?;
|
let (url, headers) = clash_client_info()?;
|
||||||
let url = format!("{url}/proxies/{name}/delay");
|
let url = format!("{url}/proxies/{name}/delay");
|
||||||
|
|
||||||
@@ -57,7 +61,7 @@ pub async fn get_proxy_delay(name: String, test_url: Option<String>) -> Result<D
|
|||||||
let builder = client
|
let builder = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
.headers(headers)
|
.headers(headers)
|
||||||
.query(&[("timeout", "10000"), ("url", &test_url)]);
|
.query(&[("timeout", &format!("{timeout}")), ("url", &test_url)]);
|
||||||
let response = builder.send().await?;
|
let response = builder.send().await?;
|
||||||
|
|
||||||
Ok(response.json::<DelayRes>().await?)
|
Ok(response.json::<DelayRes>().await?)
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ impl CoreManager {
|
|||||||
/// 切换核心
|
/// 切换核心
|
||||||
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
||||||
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
|
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
|
||||||
const CLASH_CORES: [&str; 3] = ["clash", "clash-meta", "clash-meta-alpha"];
|
const CLASH_CORES: [&str; 2] = ["clash-meta", "clash-meta-alpha"];
|
||||||
|
|
||||||
if !CLASH_CORES.contains(&clash_core.as_str()) {
|
if !CLASH_CORES.contains(&clash_core.as_str()) {
|
||||||
bail!("invalid clash core name \"{clash_core}\"");
|
bail!("invalid clash core name \"{clash_core}\"");
|
||||||
|
|||||||
@@ -58,12 +58,6 @@ impl Sysopt {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let registry_mode = {
|
|
||||||
let verge = Config::verge();
|
|
||||||
let verge = verge.latest();
|
|
||||||
verge.system_proxy_registry_mode.unwrap_or(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
let current = Sysproxy {
|
let current = Sysproxy {
|
||||||
enable,
|
enable,
|
||||||
host: String::from("127.0.0.1"),
|
host: String::from("127.0.0.1"),
|
||||||
@@ -73,15 +67,7 @@ impl Sysopt {
|
|||||||
|
|
||||||
if enable {
|
if enable {
|
||||||
let old = Sysproxy::get_system_proxy().ok();
|
let old = Sysproxy::get_system_proxy().ok();
|
||||||
|
current.set_system_proxy()?;
|
||||||
if registry_mode {
|
|
||||||
#[cfg(windows)]
|
|
||||||
current.set_system_proxy_with_registry()?;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
current.set_system_proxy()?;
|
|
||||||
} else {
|
|
||||||
current.set_system_proxy()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.old_sysproxy.lock() = old;
|
*self.old_sysproxy.lock() = old;
|
||||||
*self.cur_sysproxy.lock() = Some(current);
|
*self.cur_sysproxy.lock() = Some(current);
|
||||||
@@ -111,13 +97,6 @@ impl Sysopt {
|
|||||||
verge.system_proxy_bypass.clone(),
|
verge.system_proxy_bypass.clone(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let registry_mode = {
|
|
||||||
let verge = Config::verge();
|
|
||||||
let verge = verge.latest();
|
|
||||||
verge.system_proxy_registry_mode.unwrap_or(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut sysproxy = cur_sysproxy.take().unwrap();
|
let mut sysproxy = cur_sysproxy.take().unwrap();
|
||||||
|
|
||||||
sysproxy.enable = enable;
|
sysproxy.enable = enable;
|
||||||
@@ -129,14 +108,7 @@ impl Sysopt {
|
|||||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||||
sysproxy.port = port;
|
sysproxy.port = port;
|
||||||
|
|
||||||
if registry_mode {
|
sysproxy.set_system_proxy()?;
|
||||||
#[cfg(windows)]
|
|
||||||
sysproxy.set_system_proxy_with_registry()?;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
sysproxy.set_system_proxy()?;
|
|
||||||
} else {
|
|
||||||
sysproxy.set_system_proxy()?;
|
|
||||||
}
|
|
||||||
*cur_sysproxy = Some(sysproxy);
|
*cur_sysproxy = Some(sysproxy);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -146,11 +118,7 @@ impl Sysopt {
|
|||||||
pub fn reset_sysproxy(&self) -> Result<()> {
|
pub fn reset_sysproxy(&self) -> Result<()> {
|
||||||
let mut cur_sysproxy = self.cur_sysproxy.lock();
|
let mut cur_sysproxy = self.cur_sysproxy.lock();
|
||||||
let mut old_sysproxy = self.old_sysproxy.lock();
|
let mut old_sysproxy = self.old_sysproxy.lock();
|
||||||
let registry_mode = {
|
|
||||||
let verge = Config::verge();
|
|
||||||
let verge = verge.latest();
|
|
||||||
verge.system_proxy_registry_mode.unwrap_or(false)
|
|
||||||
};
|
|
||||||
let cur_sysproxy = cur_sysproxy.take();
|
let cur_sysproxy = cur_sysproxy.take();
|
||||||
|
|
||||||
if let Some(mut old) = old_sysproxy.take() {
|
if let Some(mut old) = old_sysproxy.take() {
|
||||||
@@ -165,26 +133,12 @@ impl Sysopt {
|
|||||||
log::info!(target: "app", "reset proxy to the original proxy");
|
log::info!(target: "app", "reset proxy to the original proxy");
|
||||||
}
|
}
|
||||||
|
|
||||||
if registry_mode {
|
old.set_system_proxy()?;
|
||||||
#[cfg(windows)]
|
|
||||||
old.set_system_proxy_with_registry()?;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
old.set_system_proxy()?;
|
|
||||||
} else {
|
|
||||||
old.set_system_proxy()?;
|
|
||||||
}
|
|
||||||
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy {
|
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy {
|
||||||
// 没有原代理,就按现在的代理设置disable即可
|
// 没有原代理,就按现在的代理设置disable即可
|
||||||
log::info!(target: "app", "reset proxy by disabling the current proxy");
|
log::info!(target: "app", "reset proxy by disabling the current proxy");
|
||||||
cur.enable = false;
|
cur.enable = false;
|
||||||
if registry_mode {
|
cur.set_system_proxy()?;
|
||||||
#[cfg(windows)]
|
|
||||||
cur.set_system_proxy_with_registry()?;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
cur.set_system_proxy()?;
|
|
||||||
} else {
|
|
||||||
cur.set_system_proxy()?;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log::info!(target: "app", "reset proxy with no action");
|
log::info!(target: "app", "reset proxy with no action");
|
||||||
}
|
}
|
||||||
@@ -303,11 +257,7 @@ impl Sysopt {
|
|||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
let guard_state = self.guard_state.clone();
|
let guard_state = self.guard_state.clone();
|
||||||
let registry_mode = {
|
|
||||||
let verge = Config::verge();
|
|
||||||
let verge = verge.latest();
|
|
||||||
verge.system_proxy_registry_mode.unwrap_or(false)
|
|
||||||
};
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
// if it is running, exit
|
// if it is running, exit
|
||||||
let mut state = guard_state.lock().await;
|
let mut state = guard_state.lock().await;
|
||||||
@@ -357,14 +307,8 @@ impl Sysopt {
|
|||||||
port,
|
port,
|
||||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||||
};
|
};
|
||||||
if registry_mode {
|
|
||||||
#[cfg(windows)]
|
log_err!(sysproxy.set_system_proxy());
|
||||||
log_err!(sysproxy.set_system_proxy_with_registry());
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
log_err!(sysproxy.set_system_proxy());
|
|
||||||
} else {
|
|
||||||
log_err!(sysproxy.set_system_proxy());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut state = guard_state.lock().await;
|
let mut state = guard_state.lock().await;
|
||||||
|
|||||||
@@ -203,14 +203,8 @@ impl Tray {
|
|||||||
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
||||||
"restart_clash" => feat::restart_clash_core(),
|
"restart_clash" => feat::restart_clash_core(),
|
||||||
"restart_app" => api::process::restart(&app_handle.env()),
|
"restart_app" => api::process::restart(&app_handle.env()),
|
||||||
"quit" => {
|
"quit" => cmds::exit_app(app_handle.clone()),
|
||||||
let _ = resolve::save_window_size_position(app_handle, true);
|
|
||||||
|
|
||||||
resolve::resolve_reset();
|
|
||||||
api::process::kill_children();
|
|
||||||
app_handle.exit(0);
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
pub const HANDLE_FIELDS: [&str; 9] = [
|
pub const HANDLE_FIELDS: [&str; 9] = [
|
||||||
"mode",
|
"mode",
|
||||||
"port",
|
|
||||||
"socks-port",
|
|
||||||
"mixed-port",
|
"mixed-port",
|
||||||
|
"socks-port",
|
||||||
|
"port",
|
||||||
"allow-lan",
|
"allow-lan",
|
||||||
"log-level",
|
"log-level",
|
||||||
"ipv6",
|
"ipv6",
|
||||||
@@ -15,71 +15,13 @@ pub const HANDLE_FIELDS: [&str; 9] = [
|
|||||||
|
|
||||||
pub const DEFAULT_FIELDS: [&str; 5] = [
|
pub const DEFAULT_FIELDS: [&str; 5] = [
|
||||||
"proxies",
|
"proxies",
|
||||||
"proxy-groups",
|
|
||||||
"proxy-providers",
|
"proxy-providers",
|
||||||
"rules",
|
"proxy-groups",
|
||||||
"rule-providers",
|
"rule-providers",
|
||||||
|
"rules",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const OTHERS_FIELDS: [&str; 31] = [
|
pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
|
||||||
"dns",
|
|
||||||
"tun",
|
|
||||||
"ebpf",
|
|
||||||
"hosts",
|
|
||||||
"script",
|
|
||||||
"profile",
|
|
||||||
"payload",
|
|
||||||
"tunnels",
|
|
||||||
"auto-redir",
|
|
||||||
"experimental",
|
|
||||||
"interface-name",
|
|
||||||
"routing-mark",
|
|
||||||
"redir-port",
|
|
||||||
"tproxy-port",
|
|
||||||
"iptables",
|
|
||||||
"external-ui",
|
|
||||||
"bind-address",
|
|
||||||
"authentication",
|
|
||||||
"tls", // meta
|
|
||||||
"sniffer", // meta
|
|
||||||
"geox-url", // meta
|
|
||||||
"listeners", // meta
|
|
||||||
"sub-rules", // meta
|
|
||||||
"geodata-mode", // meta
|
|
||||||
"unified-delay", // meta
|
|
||||||
"tcp-concurrent", // meta
|
|
||||||
"enable-process", // meta
|
|
||||||
"find-process-mode", // meta
|
|
||||||
"skip-auth-prefixes", // meta
|
|
||||||
"external-controller-tls", // meta
|
|
||||||
"global-client-fingerprint", // meta
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn use_clash_fields() -> Vec<String> {
|
|
||||||
DEFAULT_FIELDS
|
|
||||||
.into_iter()
|
|
||||||
.chain(HANDLE_FIELDS)
|
|
||||||
.chain(OTHERS_FIELDS)
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn use_valid_fields(mut valid: Vec<String>) -> Vec<String> {
|
|
||||||
let others = Vec::from(OTHERS_FIELDS);
|
|
||||||
|
|
||||||
valid.iter_mut().for_each(|s| s.make_ascii_lowercase());
|
|
||||||
valid
|
|
||||||
.into_iter()
|
|
||||||
.filter(|s| others.contains(&s.as_str()))
|
|
||||||
.chain(DEFAULT_FIELDS.iter().map(|s| s.to_string()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn use_filter(config: Mapping, filter: &Vec<String>, enable: bool) -> Mapping {
|
|
||||||
if !enable {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ret = Mapping::new();
|
let mut ret = Mapping::new();
|
||||||
|
|
||||||
for (key, value) in config.into_iter() {
|
for (key, value) in config.into_iter() {
|
||||||
@@ -105,40 +47,35 @@ pub fn use_lowercase(config: Mapping) -> Mapping {
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_sort(config: Mapping, enable_filter: bool) -> Mapping {
|
pub fn use_sort(config: Mapping) -> Mapping {
|
||||||
let mut ret = Mapping::new();
|
let mut ret = Mapping::new();
|
||||||
|
HANDLE_FIELDS.into_iter().for_each(|key| {
|
||||||
|
let key = Value::from(key);
|
||||||
|
if let Some(value) = config.get(&key) {
|
||||||
|
ret.insert(key, value.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
HANDLE_FIELDS
|
let supported_keys: HashSet<&str> = HANDLE_FIELDS.into_iter().chain(DEFAULT_FIELDS).collect();
|
||||||
|
|
||||||
|
let config_keys: HashSet<&str> = config
|
||||||
|
.keys()
|
||||||
|
.filter_map(|e| e.as_str())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(OTHERS_FIELDS)
|
.collect();
|
||||||
.chain(DEFAULT_FIELDS)
|
|
||||||
.for_each(|key| {
|
|
||||||
let key = Value::from(key);
|
|
||||||
if let Some(value) = config.get(&key) {
|
|
||||||
ret.insert(key, value.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if !enable_filter {
|
config_keys.difference(&supported_keys).for_each(|&key| {
|
||||||
let supported_keys: HashSet<&str> = HANDLE_FIELDS
|
let key = Value::from(key);
|
||||||
.into_iter()
|
if let Some(value) = config.get(&key) {
|
||||||
.chain(OTHERS_FIELDS)
|
ret.insert(key, value.clone());
|
||||||
.chain(DEFAULT_FIELDS)
|
}
|
||||||
.collect();
|
});
|
||||||
|
DEFAULT_FIELDS.into_iter().for_each(|key| {
|
||||||
let config_keys: HashSet<&str> = config
|
let key = Value::from(key);
|
||||||
.keys()
|
if let Some(value) = config.get(&key) {
|
||||||
.filter_map(|e| e.as_str())
|
ret.insert(key, value.clone());
|
||||||
.into_iter()
|
}
|
||||||
.collect();
|
});
|
||||||
|
|
||||||
config_keys.difference(&supported_keys).for_each(|&key| {
|
|
||||||
let key = Value::from(key);
|
|
||||||
if let Some(value) = config.get(&key) {
|
|
||||||
ret.insert(key, value.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
|||||||
// 直接覆盖原字段
|
// 直接覆盖原字段
|
||||||
use_lowercase(merge.clone())
|
use_lowercase(merge.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter(|(key, _)| !MERGE_FIELDS.contains(&key.as_str().unwrap_or_default()))
|
||||||
.for_each(|(key, value)| {
|
.for_each(|(key, value)| {
|
||||||
config.insert(key, value);
|
config.insert(key, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
|
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
|
||||||
let merge = use_filter(merge, &merge_list.collect(), true);
|
let merge = use_filter(merge, &merge_list.collect());
|
||||||
|
|
||||||
["rules", "proxies", "proxy-groups"]
|
["rules", "proxies", "proxy-groups"]
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
mod chain;
|
mod chain;
|
||||||
mod field;
|
pub mod field;
|
||||||
mod merge;
|
mod merge;
|
||||||
mod script;
|
mod script;
|
||||||
mod tun;
|
mod tun;
|
||||||
@@ -22,19 +22,18 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
// config.yaml 的订阅
|
// config.yaml 的订阅
|
||||||
let clash_config = { Config::clash().latest().0.clone() };
|
let clash_config = { Config::clash().latest().0.clone() };
|
||||||
|
|
||||||
let (clash_core, enable_tun, enable_builtin, enable_filter) = {
|
let (clash_core, enable_tun, enable_builtin) = {
|
||||||
let verge = Config::verge();
|
let verge = Config::verge();
|
||||||
let verge = verge.latest();
|
let verge = verge.latest();
|
||||||
(
|
(
|
||||||
verge.clash_core.clone(),
|
verge.clash_core.clone(),
|
||||||
verge.enable_tun_mode.unwrap_or(false),
|
verge.enable_tun_mode.unwrap_or(false),
|
||||||
verge.enable_builtin_enhanced.unwrap_or(true),
|
verge.enable_builtin_enhanced.unwrap_or(true),
|
||||||
verge.enable_clash_fields.unwrap_or(true),
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从profiles里拿东西
|
// 从profiles里拿东西
|
||||||
let (mut config, chain, valid) = {
|
let (mut config, chain) = {
|
||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
let profiles = profiles.latest();
|
let profiles = profiles.latest();
|
||||||
|
|
||||||
@@ -49,23 +48,17 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
None => vec![],
|
None => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let valid = profiles.valid.clone().unwrap_or_default();
|
(current, chain)
|
||||||
|
|
||||||
(current, chain, valid)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result_map = HashMap::new(); // 保存脚本日志
|
let mut result_map = HashMap::new(); // 保存脚本日志
|
||||||
let mut exists_keys = use_keys(&config); // 保存出现过的keys
|
let mut exists_keys = use_keys(&config); // 保存出现过的keys
|
||||||
|
|
||||||
let valid = use_valid_fields(valid);
|
|
||||||
config = use_filter(config, &valid, enable_filter);
|
|
||||||
|
|
||||||
// 处理用户的profile
|
// 处理用户的profile
|
||||||
chain.into_iter().for_each(|item| match item.data {
|
chain.into_iter().for_each(|item| match item.data {
|
||||||
ChainType::Merge(merge) => {
|
ChainType::Merge(merge) => {
|
||||||
exists_keys.extend(use_keys(&merge));
|
exists_keys.extend(use_keys(&merge));
|
||||||
config = use_merge(merge, config.to_owned());
|
config = use_merge(merge, config.to_owned());
|
||||||
config = use_filter(config.to_owned(), &valid, enable_filter);
|
|
||||||
}
|
}
|
||||||
ChainType::Script(script) => {
|
ChainType::Script(script) => {
|
||||||
let mut logs = vec![];
|
let mut logs = vec![];
|
||||||
@@ -73,7 +66,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
match use_script(script, config.to_owned()) {
|
match use_script(script, config.to_owned()) {
|
||||||
Ok((res_config, res_logs)) => {
|
Ok((res_config, res_logs)) => {
|
||||||
exists_keys.extend(use_keys(&res_config));
|
exists_keys.extend(use_keys(&res_config));
|
||||||
config = use_filter(res_config, &valid, enable_filter);
|
config = res_config;
|
||||||
logs.extend(res_logs);
|
logs.extend(res_logs);
|
||||||
}
|
}
|
||||||
Err(err) => logs.push(("exception".into(), err.to_string())),
|
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||||
@@ -85,11 +78,20 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
|
|
||||||
// 合并默认的config
|
// 合并默认的config
|
||||||
for (key, value) in clash_config.into_iter() {
|
for (key, value) in clash_config.into_iter() {
|
||||||
config.insert(key, value);
|
if key.as_str() == Some("tun") {
|
||||||
|
let mut tun = config.get_mut("tun").map_or(Mapping::new(), |val| {
|
||||||
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
|
});
|
||||||
|
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new());
|
||||||
|
for (key, value) in patch_tun.into_iter() {
|
||||||
|
tun.insert(key, value);
|
||||||
|
}
|
||||||
|
config.insert("tun".into(), tun.into());
|
||||||
|
} else {
|
||||||
|
config.insert(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let clash_fields = use_clash_fields();
|
|
||||||
|
|
||||||
// 内建脚本最后跑
|
// 内建脚本最后跑
|
||||||
if enable_builtin {
|
if enable_builtin {
|
||||||
ChainItem::builtin()
|
ChainItem::builtin()
|
||||||
@@ -102,7 +104,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
match item.data {
|
match item.data {
|
||||||
ChainType::Script(script) => match use_script(script, config.to_owned()) {
|
ChainType::Script(script) => match use_script(script, config.to_owned()) {
|
||||||
Ok((res_config, _)) => {
|
Ok((res_config, _)) => {
|
||||||
config = use_filter(res_config, &clash_fields, enable_filter);
|
config = res_config;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!(target: "app", "builtin script error `{err}`");
|
log::error!(target: "app", "builtin script error `{err}`");
|
||||||
@@ -113,12 +115,11 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
config = use_filter(config, &clash_fields, enable_filter);
|
|
||||||
config = use_tun(config, enable_tun);
|
config = use_tun(config, enable_tun);
|
||||||
config = use_sort(config, enable_filter);
|
config = use_sort(config);
|
||||||
|
|
||||||
let mut exists_set = HashSet::new();
|
let mut exists_set = HashSet::new();
|
||||||
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
|
exists_set.extend(exists_keys.into_iter());
|
||||||
exists_keys = exists_set.into_iter().collect();
|
exists_keys = exists_set.into_iter().collect();
|
||||||
|
|
||||||
(config, exists_keys, result_map)
|
(config, exists_keys, result_map)
|
||||||
|
|||||||
@@ -30,12 +30,6 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
|||||||
});
|
});
|
||||||
|
|
||||||
revise!(tun_val, "enable", enable);
|
revise!(tun_val, "enable", enable);
|
||||||
if enable {
|
|
||||||
append!(tun_val, "stack", "gvisor");
|
|
||||||
append!(tun_val, "dns-hijack", vec!["any:53"]);
|
|
||||||
append!(tun_val, "auto-route", true);
|
|
||||||
append!(tun_val, "auto-detect-interface", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
revise!(config, "tun", tun_val);
|
revise!(config, "tun", tun_val);
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
|||||||
|
|
||||||
match {
|
match {
|
||||||
let mixed_port = patch.get("mixed-port");
|
let mixed_port = patch.get("mixed-port");
|
||||||
|
let socks_port = patch.get("socks-port");
|
||||||
|
let port = patch.get("port");
|
||||||
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
|
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
|
||||||
if mixed_port.is_some() && !enable_random_port {
|
if mixed_port.is_some() && !enable_random_port {
|
||||||
let changed = mixed_port.unwrap()
|
let changed = mixed_port.unwrap()
|
||||||
@@ -182,6 +184,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
|||||||
|
|
||||||
// 激活订阅
|
// 激活订阅
|
||||||
if mixed_port.is_some()
|
if mixed_port.is_some()
|
||||||
|
|| socks_port.is_some()
|
||||||
|
|| port.is_some()
|
||||||
|| patch.get("secret").is_some()
|
|| patch.get("secret").is_some()
|
||||||
|| patch.get("external-controller").is_some()
|
|| patch.get("external-controller").is_some()
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-24
@@ -55,6 +55,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
cmds::get_verge_config,
|
cmds::get_verge_config,
|
||||||
cmds::patch_verge_config,
|
cmds::patch_verge_config,
|
||||||
cmds::test_delay,
|
cmds::test_delay,
|
||||||
|
cmds::exit_app,
|
||||||
// cmds::update_hotkeys,
|
// cmds::update_hotkeys,
|
||||||
// profile
|
// profile
|
||||||
cmds::get_profiles,
|
cmds::get_profiles,
|
||||||
@@ -105,34 +106,10 @@ fn main() -> std::io::Result<()> {
|
|||||||
tauri::RunEvent::ExitRequested { api, .. } => {
|
tauri::RunEvent::ExitRequested { api, .. } => {
|
||||||
api.prevent_exit();
|
api.prevent_exit();
|
||||||
}
|
}
|
||||||
tauri::RunEvent::Exit => {
|
|
||||||
resolve::resolve_reset();
|
|
||||||
api::process::kill_children();
|
|
||||||
app_handle.exit(0);
|
|
||||||
}
|
|
||||||
tauri::RunEvent::Updater(tauri::UpdaterEvent::Downloaded) => {
|
tauri::RunEvent::Updater(tauri::UpdaterEvent::Downloaded) => {
|
||||||
resolve::resolve_reset();
|
resolve::resolve_reset();
|
||||||
api::process::kill_children();
|
api::process::kill_children();
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
if label == "main" {
|
|
||||||
match event {
|
|
||||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
|
||||||
api.prevent_close();
|
|
||||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
|
||||||
|
|
||||||
app_handle.get_window("main").map(|win| {
|
|
||||||
let _ = win.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
tauri::RunEvent::WindowEvent { label, event, .. } => {
|
||||||
if label == "main" {
|
if label == "main" {
|
||||||
match event {
|
match event {
|
||||||
|
|||||||
@@ -92,14 +92,9 @@ pub fn clash_pid_path() -> Result<PathBuf> {
|
|||||||
Ok(app_home_dir()?.join("clash.pid"))
|
Ok(app_home_dir()?.join("clash.pid"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn service_dir() -> Result<PathBuf> {
|
|
||||||
Ok(app_home_dir()?.join("service"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn service_path() -> Result<PathBuf> {
|
pub fn service_path() -> Result<PathBuf> {
|
||||||
Ok(service_dir()?.join("clash-verge-service.exe"))
|
Ok(app_resources_dir()?.join("clash-verge-service.exe"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|||||||
@@ -240,67 +240,6 @@ pub fn init_resources() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// initialize service resources
|
|
||||||
/// after tauri setup
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn init_service() -> Result<()> {
|
|
||||||
let service_dir = dirs::service_dir()?;
|
|
||||||
let res_dir = dirs::app_resources_dir()?;
|
|
||||||
|
|
||||||
if !service_dir.exists() {
|
|
||||||
let _ = fs::create_dir_all(&service_dir);
|
|
||||||
}
|
|
||||||
if !res_dir.exists() {
|
|
||||||
let _ = fs::create_dir_all(&res_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_list = [
|
|
||||||
"clash-verge-service.exe",
|
|
||||||
"install-service.exe",
|
|
||||||
"uninstall-service.exe",
|
|
||||||
];
|
|
||||||
|
|
||||||
// copy the resource file
|
|
||||||
// if the source file is newer than the destination file, copy it over
|
|
||||||
for file in file_list.iter() {
|
|
||||||
let src_path = res_dir.join(file);
|
|
||||||
let dest_path = service_dir.join(file);
|
|
||||||
|
|
||||||
let handle_copy = || {
|
|
||||||
match fs::copy(&src_path, &dest_path) {
|
|
||||||
Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
|
|
||||||
Err(err) => {
|
|
||||||
log::error!(target: "app", "failed to copy resources '{file}', {err}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if src_path.exists() && !dest_path.exists() {
|
|
||||||
handle_copy();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
|
|
||||||
let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
|
|
||||||
|
|
||||||
match (src_modified, dest_modified) {
|
|
||||||
(Ok(src_modified), Ok(dest_modified)) => {
|
|
||||||
if src_modified > dest_modified {
|
|
||||||
handle_copy();
|
|
||||||
} else {
|
|
||||||
log::debug!(target: "app", "skipping resource copy '{file}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
log::debug!(target: "app", "failed to get modified '{file}'");
|
|
||||||
handle_copy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// initialize url scheme
|
/// initialize url scheme
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn init_scheme() -> Result<()> {
|
pub fn init_scheme() -> Result<()> {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use serde_yaml::Mapping;
|
|||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use tauri::api::notification;
|
use tauri::api::notification;
|
||||||
use tauri::{App, AppHandle, Manager};
|
use tauri::{App, AppHandle, Manager};
|
||||||
|
use window_shadows::set_shadow;
|
||||||
|
|
||||||
pub static VERSION: OnceCell<String> = OnceCell::new();
|
pub static VERSION: OnceCell<String> = OnceCell::new();
|
||||||
|
|
||||||
@@ -41,8 +42,6 @@ pub fn resolve_setup(app: &mut App) {
|
|||||||
VERSION.get_or_init(|| version.clone());
|
VERSION.get_or_init(|| version.clone());
|
||||||
|
|
||||||
log_err!(init::init_resources());
|
log_err!(init::init_resources());
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
log_err!(init::init_service());
|
|
||||||
log_err!(init::init_scheme());
|
log_err!(init::init_scheme());
|
||||||
log_err!(init::startup_script());
|
log_err!(init::startup_script());
|
||||||
// 处理随机端口
|
// 处理随机端口
|
||||||
@@ -156,61 +155,58 @@ pub fn create_window(app_handle: &AppHandle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
let window = builder
|
||||||
use window_shadows::set_shadow;
|
.decorations(false)
|
||||||
|
.transparent(true)
|
||||||
match builder
|
.visible(false)
|
||||||
.decorations(false)
|
.build();
|
||||||
.transparent(true)
|
|
||||||
.visible(false)
|
|
||||||
.build()
|
|
||||||
{
|
|
||||||
Ok(win) => {
|
|
||||||
log::trace!("try to calculate the monitor size");
|
|
||||||
let center = (|| -> Result<bool> {
|
|
||||||
let mut center = false;
|
|
||||||
let monitor = win.current_monitor()?.ok_or(anyhow::anyhow!(""))?;
|
|
||||||
let size = monitor.size();
|
|
||||||
let pos = win.outer_position()?;
|
|
||||||
|
|
||||||
if pos.x < -400
|
|
||||||
|| pos.x > (size.width - 200).try_into()?
|
|
||||||
|| pos.y < -200
|
|
||||||
|| pos.y > (size.height - 200).try_into()?
|
|
||||||
{
|
|
||||||
center = true;
|
|
||||||
}
|
|
||||||
Ok(center)
|
|
||||||
})();
|
|
||||||
|
|
||||||
if center.unwrap_or(true) {
|
|
||||||
trace_err!(win.center(), "set win center");
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("try to create window");
|
|
||||||
let app_handle = app_handle.clone();
|
|
||||||
|
|
||||||
if let Some(window) = app_handle.get_window("main") {
|
|
||||||
trace_err!(set_shadow(&window, true), "set win shadow");
|
|
||||||
} else {
|
|
||||||
log::error!(target: "app", "failed to create window, get_window is None")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => log::error!(target: "app", "failed to create window, {err}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
crate::log_err!(builder
|
let window = builder
|
||||||
.decorations(true)
|
.decorations(true)
|
||||||
.hidden_title(true)
|
.hidden_title(true)
|
||||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||||
.build());
|
.build();
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
crate::log_err!(builder.decorations(true).transparent(false).build());
|
let window = builder.decorations(true).transparent(false).build();
|
||||||
|
|
||||||
|
match window {
|
||||||
|
Ok(win) => {
|
||||||
|
let is_maximized = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.window_is_maximized
|
||||||
|
.unwrap_or(false);
|
||||||
|
log::trace!("try to calculate the monitor size");
|
||||||
|
let center = (|| -> Result<bool> {
|
||||||
|
let mut center = false;
|
||||||
|
let monitor = win.current_monitor()?.ok_or(anyhow::anyhow!(""))?;
|
||||||
|
let size = monitor.size();
|
||||||
|
let pos = win.outer_position()?;
|
||||||
|
|
||||||
|
if pos.x < -400
|
||||||
|
|| pos.x > (size.width - 200) as i32
|
||||||
|
|| pos.y < -200
|
||||||
|
|| pos.y > (size.height - 200) as i32
|
||||||
|
{
|
||||||
|
center = true;
|
||||||
|
}
|
||||||
|
Ok(center)
|
||||||
|
})();
|
||||||
|
|
||||||
|
if center.unwrap_or(true) {
|
||||||
|
trace_err!(win.center(), "set win center");
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_err!(set_shadow(&win, true), "set win shadow");
|
||||||
|
if is_maximized {
|
||||||
|
trace_err!(win.maximize(), "set win maximize");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to create window");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// save window size and position
|
/// save window size and position
|
||||||
@@ -231,11 +227,11 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
|
|||||||
let size = size.to_logical::<f64>(scale);
|
let size = size.to_logical::<f64>(scale);
|
||||||
let pos = win.outer_position()?;
|
let pos = win.outer_position()?;
|
||||||
let pos = pos.to_logical::<f64>(scale);
|
let pos = pos.to_logical::<f64>(scale);
|
||||||
|
let is_maximized = win.is_maximized()?;
|
||||||
if size.width >= 600.0 && size.height >= 520.0 {
|
verge.window_is_maximized = Some(is_maximized);
|
||||||
|
if !is_maximized && size.width >= 600.0 && size.height >= 520.0 {
|
||||||
verge.window_size_position = Some(vec![size.width, size.height, pos.x, pos.y]);
|
verge.window_size_position = Some(vec![size.width, size.height, pos.x, pos.y]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Clash Verge",
|
"productName": "Clash Verge",
|
||||||
"version": "1.4.10"
|
"version": "1.5.2"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../dist",
|
"distDir": "../dist",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"targets": ["deb", "appimage", "updater"],
|
"targets": ["deb", "appimage", "updater"],
|
||||||
"deb": {
|
"deb": {
|
||||||
"depends": ["openssl"],
|
"depends": ["openssl"],
|
||||||
"desktopTemplate": "./clash-verge.desktop"
|
"desktopTemplate": "./template/clash-verge.desktop"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"targets": ["app", "dmg", "updater"],
|
"targets": ["app", "dmg", "updater"],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"frameworks": [],
|
"frameworks": [],
|
||||||
"minimumSystemVersion": "",
|
"minimumSystemVersion": "10.15",
|
||||||
"exceptionDomain": "",
|
"exceptionDomain": "",
|
||||||
"signingIdentity": null,
|
"signingIdentity": null,
|
||||||
"entitlements": null
|
"entitlements": null
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
"displayLanguageSelector": true,
|
"displayLanguageSelector": true,
|
||||||
"installerIcon": "icons/icon.ico",
|
"installerIcon": "icons/icon.ico",
|
||||||
"languages": ["SimpChinese", "English"],
|
"languages": ["SimpChinese", "English"],
|
||||||
"license": "../LICENSE"
|
"license": "../LICENSE",
|
||||||
|
"installMode": "perMachine",
|
||||||
|
"template": "./template/installer.nsi"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,827 @@
|
|||||||
|
; This file is copied from https://github.com/tauri-apps/tauri/blob/tauri-v1.5/tooling/bundler/src/bundle/windows/templates/installer.nsi
|
||||||
|
; and edit to fit the needs of the project. the latest tauri 2.x has a different base nsi script.
|
||||||
|
|
||||||
|
Unicode true
|
||||||
|
; Set the compression algorithm. Default is LZMA.
|
||||||
|
!if "{{compression}}" == ""
|
||||||
|
SetCompressor /SOLID lzma
|
||||||
|
!else
|
||||||
|
SetCompressor /SOLID "{{compression}}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!include MUI2.nsh
|
||||||
|
!include FileFunc.nsh
|
||||||
|
!include x64.nsh
|
||||||
|
!include WordFunc.nsh
|
||||||
|
!include "LogicLib.nsh"
|
||||||
|
!include "StrFunc.nsh"
|
||||||
|
!addplugindir "$%AppData%\Local\NSIS\"
|
||||||
|
${StrCase}
|
||||||
|
${StrLoc}
|
||||||
|
|
||||||
|
!define MANUFACTURER "{{manufacturer}}"
|
||||||
|
!define PRODUCTNAME "{{product_name}}"
|
||||||
|
!define VERSION "{{version}}"
|
||||||
|
!define VERSIONWITHBUILD "{{version_with_build}}"
|
||||||
|
!define SHORTDESCRIPTION "{{short_description}}"
|
||||||
|
!define INSTALLMODE "{{install_mode}}"
|
||||||
|
!define LICENSE "{{license}}"
|
||||||
|
!define INSTALLERICON "{{installer_icon}}"
|
||||||
|
!define SIDEBARIMAGE "{{sidebar_image}}"
|
||||||
|
!define HEADERIMAGE "{{header_image}}"
|
||||||
|
!define MAINBINARYNAME "{{main_binary_name}}"
|
||||||
|
!define MAINBINARYSRCPATH "{{main_binary_path}}"
|
||||||
|
!define BUNDLEID "{{bundle_id}}"
|
||||||
|
!define COPYRIGHT "{{copyright}}"
|
||||||
|
!define OUTFILE "{{out_file}}"
|
||||||
|
!define ARCH "{{arch}}"
|
||||||
|
!define PLUGINSPATH "{{additional_plugins_path}}"
|
||||||
|
!define ALLOWDOWNGRADES "{{allow_downgrades}}"
|
||||||
|
!define DISPLAYLANGUAGESELECTOR "{{display_language_selector}}"
|
||||||
|
!define INSTALLWEBVIEW2MODE "{{install_webview2_mode}}"
|
||||||
|
!define WEBVIEW2INSTALLERARGS "{{webview2_installer_args}}"
|
||||||
|
!define WEBVIEW2BOOTSTRAPPERPATH "{{webview2_bootstrapper_path}}"
|
||||||
|
!define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
|
||||||
|
!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
|
||||||
|
!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
|
||||||
|
!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
|
||||||
|
!define ESTIMATEDSIZE "{{estimated_size}}"
|
||||||
|
|
||||||
|
Name "${PRODUCTNAME}"
|
||||||
|
BrandingText "${COPYRIGHT}"
|
||||||
|
OutFile "${OUTFILE}"
|
||||||
|
|
||||||
|
VIProductVersion "${VERSIONWITHBUILD}"
|
||||||
|
VIAddVersionKey "ProductName" "${PRODUCTNAME}"
|
||||||
|
VIAddVersionKey "FileDescription" "${SHORTDESCRIPTION}"
|
||||||
|
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||||
|
VIAddVersionKey "FileVersion" "${VERSION}"
|
||||||
|
VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||||
|
|
||||||
|
; Plugins path, currently exists for linux only
|
||||||
|
!if "${PLUGINSPATH}" != ""
|
||||||
|
!addplugindir "${PLUGINSPATH}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "${UNINSTALLERSIGNCOMMAND}" != ""
|
||||||
|
!uninstfinalize '${UNINSTALLERSIGNCOMMAND}'
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; Handle install mode, `perUser`, `perMachine` or `both`
|
||||||
|
!if "${INSTALLMODE}" == "perMachine"
|
||||||
|
RequestExecutionLevel highest
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
RequestExecutionLevel user
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "${INSTALLMODE}" == "both"
|
||||||
|
!define MULTIUSER_MUI
|
||||||
|
!define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}"
|
||||||
|
!define MULTIUSER_INSTALLMODE_COMMANDLINE
|
||||||
|
!if "${ARCH}" == "x64"
|
||||||
|
!define MULTIUSER_USE_PROGRAMFILES64
|
||||||
|
!else if "${ARCH}" == "arm64"
|
||||||
|
!define MULTIUSER_USE_PROGRAMFILES64
|
||||||
|
!endif
|
||||||
|
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}"
|
||||||
|
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser"
|
||||||
|
!define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME
|
||||||
|
!define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation
|
||||||
|
!define MULTIUSER_EXECUTIONLEVEL Highest
|
||||||
|
!include MultiUser.nsh
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; installer icon
|
||||||
|
!if "${INSTALLERICON}" != ""
|
||||||
|
!define MUI_ICON "${INSTALLERICON}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; installer sidebar image
|
||||||
|
!if "${SIDEBARIMAGE}" != ""
|
||||||
|
!define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; installer header image
|
||||||
|
!if "${HEADERIMAGE}" != ""
|
||||||
|
!define MUI_HEADERIMAGE
|
||||||
|
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; Define registry key to store installer language
|
||||||
|
!define MUI_LANGDLL_REGISTRY_ROOT "HKCU"
|
||||||
|
!define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}"
|
||||||
|
!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
|
||||||
|
|
||||||
|
; Installer pages, must be ordered as they appear
|
||||||
|
; 1. Welcome Page
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
|
||||||
|
; 2. License Page (if defined)
|
||||||
|
!if "${LICENSE}" != ""
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "${LICENSE}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; 3. Install mode (if it is set to `both`)
|
||||||
|
!if "${INSTALLMODE}" == "both"
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||||
|
!insertmacro MULTIUSER_PAGE_INSTALLMODE
|
||||||
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
; 4. Custom page to ask user if he wants to reinstall/uninstall
|
||||||
|
; only if a previous installtion was detected
|
||||||
|
Var ReinstallPageCheck
|
||||||
|
Page custom PageReinstall PageLeaveReinstall
|
||||||
|
Function PageReinstall
|
||||||
|
; Uninstall previous WiX installation if exists.
|
||||||
|
;
|
||||||
|
; A WiX installer stores the isntallation info in registry
|
||||||
|
; using a UUID and so we have to loop through all keys under
|
||||||
|
; `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`
|
||||||
|
; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER}
|
||||||
|
;
|
||||||
|
; This has a potentional issue that there maybe another installation that matches
|
||||||
|
; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer,
|
||||||
|
; however, this should be fine since the user will have to confirm the uninstallation
|
||||||
|
; and they can chose to abort it if doesn't make sense.
|
||||||
|
StrCpy $0 0
|
||||||
|
|
||||||
|
wix_loop:
|
||||||
|
EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
|
||||||
|
StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on
|
||||||
|
IntOp $0 $0 + 1
|
||||||
|
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName"
|
||||||
|
ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher"
|
||||||
|
StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop
|
||||||
|
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString"
|
||||||
|
${StrCase} $R1 $R0 "L"
|
||||||
|
${StrLoc} $R0 $R1 "msiexec" ">"
|
||||||
|
StrCmp $R0 0 0 wix_done
|
||||||
|
StrCpy $R7 "wix"
|
||||||
|
StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1"
|
||||||
|
Goto compare_version
|
||||||
|
wix_done:
|
||||||
|
|
||||||
|
; Check if there is an existing installation, if not, abort the reinstall page
|
||||||
|
ReadRegStr $R0 SHCTX "${UNINSTKEY}" ""
|
||||||
|
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
|
||||||
|
${IfThen} "$R0$R1" == "" ${|} Abort ${|}
|
||||||
|
|
||||||
|
; Compare this installar version with the existing installation
|
||||||
|
; and modify the messages presented to the user accordingly
|
||||||
|
compare_version:
|
||||||
|
StrCpy $R4 "$(older)"
|
||||||
|
${If} $R7 == "wix"
|
||||||
|
ReadRegStr $R0 HKLM "$R6" "DisplayVersion"
|
||||||
|
${Else}
|
||||||
|
ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion"
|
||||||
|
${EndIf}
|
||||||
|
${IfThen} $R0 == "" ${|} StrCpy $R4 "$(unknown)" ${|}
|
||||||
|
|
||||||
|
nsis_tauri_utils::SemverCompare "${VERSION}" $R0
|
||||||
|
Pop $R0
|
||||||
|
; Reinstalling the same version
|
||||||
|
${If} $R0 == 0
|
||||||
|
StrCpy $R1 "$(alreadyInstalledLong)"
|
||||||
|
StrCpy $R2 "$(addOrReinstall)"
|
||||||
|
StrCpy $R3 "$(uninstallApp)"
|
||||||
|
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)"
|
||||||
|
StrCpy $R5 "2"
|
||||||
|
; Upgrading
|
||||||
|
${ElseIf} $R0 == 1
|
||||||
|
StrCpy $R1 "$(olderOrUnknownVersionInstalled)"
|
||||||
|
StrCpy $R2 "$(uninstallBeforeInstalling)"
|
||||||
|
StrCpy $R3 "$(dontUninstall)"
|
||||||
|
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
|
||||||
|
StrCpy $R5 "1"
|
||||||
|
; Downgrading
|
||||||
|
${ElseIf} $R0 == -1
|
||||||
|
StrCpy $R1 "$(newerVersionInstalled)"
|
||||||
|
StrCpy $R2 "$(uninstallBeforeInstalling)"
|
||||||
|
!if "${ALLOWDOWNGRADES}" == "true"
|
||||||
|
StrCpy $R3 "$(dontUninstall)"
|
||||||
|
!else
|
||||||
|
StrCpy $R3 "$(dontUninstallDowngrade)"
|
||||||
|
!endif
|
||||||
|
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
|
||||||
|
StrCpy $R5 "1"
|
||||||
|
${Else}
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
Call SkipIfPassive
|
||||||
|
|
||||||
|
nsDialogs::Create 1018
|
||||||
|
Pop $R4
|
||||||
|
${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}
|
||||||
|
|
||||||
|
${NSD_CreateLabel} 0 0 100% 24u $R1
|
||||||
|
Pop $R1
|
||||||
|
|
||||||
|
${NSD_CreateRadioButton} 30u 50u -30u 8u $R2
|
||||||
|
Pop $R2
|
||||||
|
${NSD_OnClick} $R2 PageReinstallUpdateSelection
|
||||||
|
|
||||||
|
${NSD_CreateRadioButton} 30u 70u -30u 8u $R3
|
||||||
|
Pop $R3
|
||||||
|
; disable this radio button if downgrading and downgrades are disabled
|
||||||
|
!if "${ALLOWDOWNGRADES}" == "false"
|
||||||
|
${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|}
|
||||||
|
!endif
|
||||||
|
${NSD_OnClick} $R3 PageReinstallUpdateSelection
|
||||||
|
|
||||||
|
; Check the first radio button if this the first time
|
||||||
|
; we enter this page or if the second button wasn't
|
||||||
|
; selected the last time we were on this page
|
||||||
|
${If} $ReinstallPageCheck != 2
|
||||||
|
SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||||
|
${Else}
|
||||||
|
SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${NSD_SetFocus} $R2
|
||||||
|
nsDialogs::Show
|
||||||
|
FunctionEnd
|
||||||
|
Function PageReinstallUpdateSelection
|
||||||
|
${NSD_GetState} $R2 $R1
|
||||||
|
${If} $R1 == ${BST_CHECKED}
|
||||||
|
StrCpy $ReinstallPageCheck 1
|
||||||
|
${Else}
|
||||||
|
StrCpy $ReinstallPageCheck 2
|
||||||
|
${EndIf}
|
||||||
|
FunctionEnd
|
||||||
|
Function PageLeaveReinstall
|
||||||
|
${NSD_GetState} $R2 $R1
|
||||||
|
|
||||||
|
; $R5 holds whether we are reinstalling the same version or not
|
||||||
|
; $R5 == "1" -> different versions
|
||||||
|
; $R5 == "2" -> same version
|
||||||
|
;
|
||||||
|
; $R1 holds the radio buttons state. its meaning is dependant on the context
|
||||||
|
StrCmp $R5 "1" 0 +2 ; Existing install is not the same version?
|
||||||
|
StrCmp $R1 "1" reinst_uninstall reinst_done ; $R1 == "1", then user chose to uninstall existing version, otherwise skip uninstalling
|
||||||
|
StrCmp $R1 "1" reinst_done ; Same version? skip uninstalling
|
||||||
|
|
||||||
|
reinst_uninstall:
|
||||||
|
HideWindow
|
||||||
|
ClearErrors
|
||||||
|
|
||||||
|
${If} $R7 == "wix"
|
||||||
|
ReadRegStr $R1 HKLM "$R6" "UninstallString"
|
||||||
|
ExecWait '$R1' $0
|
||||||
|
${Else}
|
||||||
|
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
|
||||||
|
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
|
||||||
|
ExecWait '$R1 /P _?=$4' $0
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
BringToFront
|
||||||
|
|
||||||
|
${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code
|
||||||
|
|
||||||
|
${If} $0 <> 0
|
||||||
|
${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
${If} $0 = 1 ; User aborted uninstaller?
|
||||||
|
StrCmp $R5 "2" 0 +2 ; Is the existing install the same version?
|
||||||
|
Quit ; ...yes, already installed, we are done
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
MessageBox MB_ICONEXCLAMATION "$(unableToUninstall)"
|
||||||
|
Abort
|
||||||
|
${Else}
|
||||||
|
StrCpy $0 $R1 1
|
||||||
|
${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString
|
||||||
|
Delete $R1
|
||||||
|
RMDir $INSTDIR
|
||||||
|
${EndIf}
|
||||||
|
reinst_done:
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
; 5. Choose install directoy page
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
|
||||||
|
; 6. Start menu shortcut page
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||||
|
Var AppStartMenuFolder
|
||||||
|
!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder
|
||||||
|
|
||||||
|
; 7. Installation page
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
|
||||||
|
; 8. Finish page
|
||||||
|
;
|
||||||
|
; Don't auto jump to finish page after installation page,
|
||||||
|
; because the installation page has useful info that can be used debug any issues with the installer.
|
||||||
|
!define MUI_FINISHPAGE_NOAUTOCLOSE
|
||||||
|
; Use show readme button in the finish page as a button create a desktop shortcut
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
|
||||||
|
; Show run app after installation.
|
||||||
|
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
; Uninstaller Pages
|
||||||
|
; 1. Confirm uninstall page
|
||||||
|
Var DeleteAppDataCheckbox
|
||||||
|
Var DeleteAppDataCheckboxState
|
||||||
|
!define /ifndef WS_EX_LAYOUTRTL 0x00400000
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow
|
||||||
|
Function un.ConfirmShow
|
||||||
|
FindWindow $1 "#32770" "" $HWNDPARENT ; Find inner dialog
|
||||||
|
${If} $(^RTL) == 1
|
||||||
|
System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
|
||||||
|
${Else}
|
||||||
|
System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
|
||||||
|
${EndIf}
|
||||||
|
Pop $DeleteAppDataCheckbox
|
||||||
|
SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1
|
||||||
|
SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1
|
||||||
|
FunctionEnd
|
||||||
|
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave
|
||||||
|
Function un.ConfirmLeave
|
||||||
|
SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState
|
||||||
|
FunctionEnd
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
|
||||||
|
; 2. Uninstalling Page
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
|
||||||
|
;Languages
|
||||||
|
{{#each languages}}
|
||||||
|
!insertmacro MUI_LANGUAGE "{{this}}"
|
||||||
|
{{/each}}
|
||||||
|
!insertmacro MUI_RESERVEFILE_LANGDLL
|
||||||
|
{{#each language_files}}
|
||||||
|
!include "{{this}}"
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
!macro SetContext
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
SetShellVarContext current
|
||||||
|
!else if "${INSTALLMODE}" == "perMachine"
|
||||||
|
SetShellVarContext all
|
||||||
|
!endif
|
||||||
|
|
||||||
|
${If} ${RunningX64}
|
||||||
|
!if "${ARCH}" == "x64"
|
||||||
|
SetRegView 64
|
||||||
|
!else if "${ARCH}" == "arm64"
|
||||||
|
SetRegView 64
|
||||||
|
!else
|
||||||
|
SetRegView 32
|
||||||
|
!endif
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
Var PassiveMode
|
||||||
|
Function .onInit
|
||||||
|
${GetOptions} $CMDLINE "/P" $PassiveMode
|
||||||
|
IfErrors +2 0
|
||||||
|
StrCpy $PassiveMode 1
|
||||||
|
|
||||||
|
!if "${DISPLAYLANGUAGESELECTOR}" == "true"
|
||||||
|
!insertmacro MUI_LANGDLL_DISPLAY
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!insertmacro SetContext
|
||||||
|
|
||||||
|
${If} $INSTDIR == ""
|
||||||
|
; Set default install location
|
||||||
|
!if "${INSTALLMODE}" == "perMachine"
|
||||||
|
${If} ${RunningX64}
|
||||||
|
!if "${ARCH}" == "x64"
|
||||||
|
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
|
||||||
|
!else if "${ARCH}" == "arm64"
|
||||||
|
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
|
||||||
|
!else
|
||||||
|
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
|
||||||
|
!endif
|
||||||
|
${Else}
|
||||||
|
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
|
||||||
|
${EndIf}
|
||||||
|
!else if "${INSTALLMODE}" == "currentUser"
|
||||||
|
StrCpy $INSTDIR "$LOCALAPPDATA\${PRODUCTNAME}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
Call RestorePreviousInstallLocation
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
|
||||||
|
!if "${INSTALLMODE}" == "both"
|
||||||
|
!insertmacro MULTIUSER_INIT
|
||||||
|
!endif
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
!macro CheckAllVergeProcesses
|
||||||
|
; Check if Clash Verge.exe is running
|
||||||
|
nsis_tauri_utils::FindProcess "Clash Verge.exe"
|
||||||
|
${If} $R0 != 0
|
||||||
|
; Kill the process
|
||||||
|
DetailPrint "Kill Clash Verge.exe..."
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
nsis_tauri_utils::KillProcessCurrentUser "Clash Verge.exe"
|
||||||
|
!else
|
||||||
|
nsis_tauri_utils::KillProcess "Clash Verge.exe"
|
||||||
|
!endif
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
|
||||||
|
; Check if clash-verge-service.exe is running
|
||||||
|
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
|
||||||
|
${If} $R0 != 0
|
||||||
|
; Kill the process
|
||||||
|
DetailPrint "Kill clash-verge-service.exe..."
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
|
||||||
|
!else
|
||||||
|
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
|
||||||
|
!endif
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
|
||||||
|
; Check if clash-meta-alpha.exe is running
|
||||||
|
nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
|
||||||
|
${If} $R0 != 0
|
||||||
|
; Kill the process
|
||||||
|
DetailPrint "Kill clash-meta-alpha.exe..."
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
|
||||||
|
!else
|
||||||
|
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
|
||||||
|
!endif
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
; Check if clash-meta.exe is running
|
||||||
|
nsis_tauri_utils::FindProcess "clash-meta.exe"
|
||||||
|
${If} $R0 != 0
|
||||||
|
; Kill the process
|
||||||
|
DetailPrint "Kill clash-meta.exe..."
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
|
||||||
|
!else
|
||||||
|
nsis_tauri_utils::KillProcess "clash-meta.exe"
|
||||||
|
!endif
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro StartVergeService
|
||||||
|
; Check if the service exists
|
||||||
|
SimpleSC::ExistsService "clash_verge_service"
|
||||||
|
Pop $0 ; 0:service exists;other: service not exists
|
||||||
|
; Service exists
|
||||||
|
${If} $0 == 0
|
||||||
|
Push $0
|
||||||
|
; Check if the service is running
|
||||||
|
SimpleSC::ServiceIsRunning "clash_verge_service"
|
||||||
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
|
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||||
|
${If} $0 == 0
|
||||||
|
Push $0
|
||||||
|
${If} $1 == 0
|
||||||
|
DetailPrint "Restart Clash Verge Service..."
|
||||||
|
SimpleSC::StartService "clash_verge_service" "" 30
|
||||||
|
${EndIf}
|
||||||
|
${ElseIf} $0 != 0
|
||||||
|
Push $0
|
||||||
|
SimpleSC::GetErrorMessage
|
||||||
|
Pop $0
|
||||||
|
MessageBox MB_OK|MB_ICONSTOP "Check Service Status Error ($0)"
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro RemoveVergeService
|
||||||
|
; Check if the service exists
|
||||||
|
SimpleSC::ExistsService "clash_verge_service"
|
||||||
|
Pop $0 ; 0:service exists;other: service not exists
|
||||||
|
; Service exists
|
||||||
|
${If} $0 == 0
|
||||||
|
Push $0
|
||||||
|
; Check if the service is running
|
||||||
|
SimpleSC::ServiceIsRunning "clash_verge_service"
|
||||||
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
|
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
|
||||||
|
${If} $0 == 0
|
||||||
|
Push $0
|
||||||
|
${If} $1 == 1
|
||||||
|
DetailPrint "Stop Clash Verge Service..."
|
||||||
|
SimpleSC::StopService "clash_verge_service" 1 30
|
||||||
|
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
|
||||||
|
${If} $0 == 0
|
||||||
|
DetailPrint "Removing Clash Verge Service..."
|
||||||
|
SimpleSC::RemoveService "clash_verge_service"
|
||||||
|
${ElseIf} $0 != 0
|
||||||
|
Push $0
|
||||||
|
SimpleSC::GetErrorMessage
|
||||||
|
Pop $0
|
||||||
|
MessageBox MB_OK|MB_ICONSTOP "Clash Verge Service Stop Error ($0)"
|
||||||
|
${EndIf}
|
||||||
|
${ElseIf} $1 == 0
|
||||||
|
DetailPrint "Removing Clash Verge Service..."
|
||||||
|
SimpleSC::RemoveService "clash_verge_service"
|
||||||
|
${EndIf}
|
||||||
|
${ElseIf} $0 != 0
|
||||||
|
Push $0
|
||||||
|
SimpleSC::GetErrorMessage
|
||||||
|
Pop $0
|
||||||
|
MessageBox MB_OK|MB_ICONSTOP "Check Service Status Error ($0)"
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
Section EarlyChecks
|
||||||
|
; Abort silent installer if downgrades is disabled
|
||||||
|
!if "${ALLOWDOWNGRADES}" == "false"
|
||||||
|
IfSilent 0 silent_downgrades_done
|
||||||
|
; If downgrading
|
||||||
|
${If} $R0 == -1
|
||||||
|
System::Call 'kernel32::AttachConsole(i -1)i.r0'
|
||||||
|
${If} $0 != 0
|
||||||
|
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
|
||||||
|
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
|
||||||
|
FileWrite $0 "$(silentDowngrades)"
|
||||||
|
${EndIf}
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
silent_downgrades_done:
|
||||||
|
!endif
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section WebView2
|
||||||
|
; Check if Webview2 is already installed and skip this section
|
||||||
|
${If} ${RunningX64}
|
||||||
|
ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||||
|
${Else}
|
||||||
|
ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||||
|
${EndIf}
|
||||||
|
ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||||
|
|
||||||
|
StrCmp $4 "" 0 webview2_done
|
||||||
|
StrCmp $5 "" 0 webview2_done
|
||||||
|
|
||||||
|
; Webview2 install modes
|
||||||
|
!if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
|
||||||
|
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||||
|
DetailPrint "$(webview2Downloading)"
|
||||||
|
nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||||
|
Pop $0
|
||||||
|
${If} $0 == 0
|
||||||
|
DetailPrint "$(webview2DownloadSuccess)"
|
||||||
|
${Else}
|
||||||
|
DetailPrint "$(webview2DownloadError)"
|
||||||
|
Abort "$(webview2AbortError)"
|
||||||
|
${EndIf}
|
||||||
|
StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||||
|
Goto install_webview2
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper"
|
||||||
|
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||||
|
File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}"
|
||||||
|
DetailPrint "$(installingWebview2)"
|
||||||
|
StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||||
|
Goto install_webview2
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
|
||||||
|
Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
|
||||||
|
File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
|
||||||
|
DetailPrint "$(installingWebview2)"
|
||||||
|
StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
|
||||||
|
Goto install_webview2
|
||||||
|
!endif
|
||||||
|
|
||||||
|
Goto webview2_done
|
||||||
|
|
||||||
|
install_webview2:
|
||||||
|
DetailPrint "$(installingWebview2)"
|
||||||
|
; $6 holds the path to the webview2 installer
|
||||||
|
ExecWait "$6 ${WEBVIEW2INSTALLERARGS} /install" $1
|
||||||
|
${If} $1 == 0
|
||||||
|
DetailPrint "$(webview2InstallSuccess)"
|
||||||
|
${Else}
|
||||||
|
DetailPrint "$(webview2InstallError)"
|
||||||
|
Abort "$(webview2AbortError)"
|
||||||
|
${EndIf}
|
||||||
|
webview2_done:
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
!macro CheckIfAppIsRunning
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
nsis_tauri_utils::FindProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||||
|
!else
|
||||||
|
nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe"
|
||||||
|
!endif
|
||||||
|
Pop $R0
|
||||||
|
${If} $R0 = 0
|
||||||
|
IfSilent kill 0
|
||||||
|
${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|}
|
||||||
|
kill:
|
||||||
|
!if "${INSTALLMODE}" == "currentUser"
|
||||||
|
nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||||
|
!else
|
||||||
|
nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
|
||||||
|
!endif
|
||||||
|
Pop $R0
|
||||||
|
Sleep 500
|
||||||
|
${If} $R0 = 0
|
||||||
|
Goto app_check_done
|
||||||
|
${Else}
|
||||||
|
IfSilent silent ui
|
||||||
|
silent:
|
||||||
|
System::Call 'kernel32::AttachConsole(i -1)i.r0'
|
||||||
|
${If} $0 != 0
|
||||||
|
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
|
||||||
|
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
|
||||||
|
FileWrite $0 "$(appRunning)$\n"
|
||||||
|
${EndIf}
|
||||||
|
Abort
|
||||||
|
ui:
|
||||||
|
Abort "$(failedToKillApp)"
|
||||||
|
${EndIf}
|
||||||
|
cancel:
|
||||||
|
Abort "$(appRunning)"
|
||||||
|
${EndIf}
|
||||||
|
app_check_done:
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
Section Install
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
|
||||||
|
!insertmacro CheckIfAppIsRunning
|
||||||
|
!insertmacro CheckAllVergeProcesses
|
||||||
|
; Copy main executable
|
||||||
|
File "${MAINBINARYSRCPATH}"
|
||||||
|
|
||||||
|
; Copy resources
|
||||||
|
{{#each resources_dirs}}
|
||||||
|
CreateDirectory "$INSTDIR\\{{this}}"
|
||||||
|
{{/each}}
|
||||||
|
{{#each resources}}
|
||||||
|
File /a "/oname={{this.[1]}}" "{{@key}}"
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
; Copy external binaries
|
||||||
|
{{#each binaries}}
|
||||||
|
File /a "/oname={{this}}" "{{@key}}"
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
!insertmacro StartVergeService
|
||||||
|
|
||||||
|
; Create uninstaller
|
||||||
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
; Save $INSTDIR in registry for future installations
|
||||||
|
WriteRegStr SHCTX "${MANUPRODUCTKEY}" "" $INSTDIR
|
||||||
|
|
||||||
|
!if "${INSTALLMODE}" == "both"
|
||||||
|
; Save install mode to be selected by default for the next installation such as updating
|
||||||
|
; or when uninstalling
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1
|
||||||
|
!endif
|
||||||
|
|
||||||
|
; Registry information for add/remove programs
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}"
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\""
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}"
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}"
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\""
|
||||||
|
WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||||
|
WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1"
|
||||||
|
WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1"
|
||||||
|
WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "${ESTIMATEDSIZE}"
|
||||||
|
|
||||||
|
; Create start menu shortcut (GUI)
|
||||||
|
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||||
|
Call CreateStartMenuShortcut
|
||||||
|
!insertmacro MUI_STARTMENU_WRITE_END
|
||||||
|
|
||||||
|
; Create shortcuts for silent and passive installers, which
|
||||||
|
; can be disabled by passing `/NS` flag
|
||||||
|
; GUI installer has buttons for users to control creating them
|
||||||
|
IfSilent check_ns_flag 0
|
||||||
|
${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|}
|
||||||
|
Goto shortcuts_done
|
||||||
|
check_ns_flag:
|
||||||
|
${GetOptions} $CMDLINE "/NS" $R0
|
||||||
|
IfErrors 0 shortcuts_done
|
||||||
|
Call CreateDesktopShortcut
|
||||||
|
Call CreateStartMenuShortcut
|
||||||
|
shortcuts_done:
|
||||||
|
|
||||||
|
; Auto close this page for passive mode
|
||||||
|
${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|}
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Function .onInstSuccess
|
||||||
|
; Check for `/R` flag only in silent and passive installers because
|
||||||
|
; GUI installer has a toggle for the user to (re)start the app
|
||||||
|
IfSilent check_r_flag 0
|
||||||
|
${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|}
|
||||||
|
Goto run_done
|
||||||
|
check_r_flag:
|
||||||
|
${GetOptions} $CMDLINE "/R" $R0
|
||||||
|
IfErrors run_done 0
|
||||||
|
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
|
||||||
|
run_done:
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function un.onInit
|
||||||
|
!insertmacro SetContext
|
||||||
|
|
||||||
|
!if "${INSTALLMODE}" == "both"
|
||||||
|
!insertmacro MULTIUSER_UNINIT
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!insertmacro MUI_UNGETLANGUAGE
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section Uninstall
|
||||||
|
!insertmacro CheckIfAppIsRunning
|
||||||
|
!insertmacro CheckAllVergeProcesses
|
||||||
|
!insertmacro RemoveVergeService
|
||||||
|
; Delete the app directory and its content from disk
|
||||||
|
; Copy main executable
|
||||||
|
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
|
||||||
|
; Delete resources
|
||||||
|
{{#each resources}}
|
||||||
|
Delete "$INSTDIR\\{{this.[1]}}"
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
; Delete external binaries
|
||||||
|
{{#each binaries}}
|
||||||
|
Delete "$INSTDIR\\{{this}}"
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
; Delete uninstaller
|
||||||
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
${If} $DeleteAppDataCheckboxState == 1
|
||||||
|
RMDir /R /REBOOTOK "$INSTDIR"
|
||||||
|
${Else}
|
||||||
|
{{#each resources_ancestors}}
|
||||||
|
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
|
||||||
|
{{/each}}
|
||||||
|
RMDir "$INSTDIR"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
; Remove start menu shortcut
|
||||||
|
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
||||||
|
Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
|
||||||
|
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
|
||||||
|
|
||||||
|
; Remove desktop shortcuts
|
||||||
|
Delete "$DESKTOP\${MAINBINARYNAME}.lnk"
|
||||||
|
|
||||||
|
; Remove registry information for add/remove programs
|
||||||
|
!if "${INSTALLMODE}" == "both"
|
||||||
|
DeleteRegKey SHCTX "${UNINSTKEY}"
|
||||||
|
!else if "${INSTALLMODE}" == "perMachine"
|
||||||
|
DeleteRegKey HKLM "${UNINSTKEY}"
|
||||||
|
!else
|
||||||
|
DeleteRegKey HKCU "${UNINSTKEY}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
|
||||||
|
|
||||||
|
; Delete app data
|
||||||
|
${If} $DeleteAppDataCheckboxState == 1
|
||||||
|
SetShellVarContext current
|
||||||
|
RmDir /r "$APPDATA\${BUNDLEID}"
|
||||||
|
RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${GetOptions} $CMDLINE "/P" $R0
|
||||||
|
IfErrors +2 0
|
||||||
|
SetAutoClose true
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Function RestorePreviousInstallLocation
|
||||||
|
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
|
||||||
|
StrCmp $4 "" +2 0
|
||||||
|
StrCpy $INSTDIR $4
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function SkipIfPassive
|
||||||
|
${IfThen} $PassiveMode == 1 ${|} Abort ${|}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function CreateDesktopShortcut
|
||||||
|
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function CreateStartMenuShortcut
|
||||||
|
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
|
||||||
|
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
|
||||||
|
FunctionEnd
|
||||||
@@ -27,8 +27,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 8px;
|
||||||
height: 6px;
|
height: 8px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
.base-container {
|
.base-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: var(--border-radius);
|
border-top-left-radius: var(--border-radius);
|
||||||
|
|
||||||
> section {
|
> section {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ export const ProviderButton = () => {
|
|||||||
>
|
>
|
||||||
{key}
|
{key}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<TypeBox component="span" sx={{ marginLeft: "8px" }}>
|
||||||
|
{item.proxies.length}
|
||||||
|
</TypeBox>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
secondary={
|
secondary={
|
||||||
@@ -158,6 +161,17 @@ export const ProviderButton = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const TypeBox = styled(Box)(({ theme }) => ({
|
||||||
|
display: "inline-block",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderColor: alpha(theme.palette.secondary.main, 0.5),
|
||||||
|
color: alpha(theme.palette.secondary.main, 0.8),
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 10,
|
||||||
|
marginRight: "4px",
|
||||||
|
padding: "0 2px",
|
||||||
|
lineHeight: 1.25,
|
||||||
|
}));
|
||||||
|
|
||||||
const StyledTypeBox = styled(Box)(({ theme }) => ({
|
const StyledTypeBox = styled(Box)(({ theme }) => ({
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
|
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { current, patchCurrent } = useProfiles();
|
const { current, patchCurrent } = useProfiles();
|
||||||
|
const timeout = verge?.default_latency_timeout || 10000;
|
||||||
|
|
||||||
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
|
const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
|
||||||
await delayManager.checkListDelay(names, groupName);
|
await delayManager.checkListDelay(names, groupName, timeout);
|
||||||
|
|
||||||
onProxies();
|
onProxies();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { CheckCircleOutlineRounded } from "@mui/icons-material";
|
|||||||
import { alpha, Box, ListItemButton, styled, Typography } from "@mui/material";
|
import { alpha, Box, ListItemButton, styled, Typography } from "@mui/material";
|
||||||
import { BaseLoading } from "@/components/base";
|
import { BaseLoading } from "@/components/base";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
groupName: string;
|
groupName: string;
|
||||||
@@ -20,6 +21,8 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
// -1/<=0 为 不显示
|
// -1/<=0 为 不显示
|
||||||
// -2 为 loading
|
// -2 为 loading
|
||||||
const [delay, setDelay] = useState(-1);
|
const [delay, setDelay] = useState(-1);
|
||||||
|
const { verge } = useVerge();
|
||||||
|
const timeout = verge?.default_latency_timeout || 10000;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||||
@@ -36,7 +39,7 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
|
|
||||||
const onDelay = useLockFn(async () => {
|
const onDelay = useLockFn(async () => {
|
||||||
setDelay(-2);
|
setDelay(-2);
|
||||||
setDelay(await delayManager.checkDelay(proxy.name, groupName));
|
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -139,14 +142,14 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelay();
|
onDelay();
|
||||||
}}
|
}}
|
||||||
color={delayManager.formatDelayColor(delay)}
|
color={delayManager.formatDelayColor(delay, timeout)}
|
||||||
sx={({ palette }) =>
|
sx={({ palette }) =>
|
||||||
!proxy.provider
|
!proxy.provider
|
||||||
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{delayManager.formatDelay(delay)}
|
{delayManager.formatDelay(delay, timeout)}
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { BaseLoading } from "@/components/base";
|
import { BaseLoading } from "@/components/base";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
groupName: string;
|
groupName: string;
|
||||||
@@ -48,7 +49,8 @@ export const ProxyItem = (props: Props) => {
|
|||||||
// -1/<=0 为 不显示
|
// -1/<=0 为 不显示
|
||||||
// -2 为 loading
|
// -2 为 loading
|
||||||
const [delay, setDelay] = useState(-1);
|
const [delay, setDelay] = useState(-1);
|
||||||
|
const { verge } = useVerge();
|
||||||
|
const timeout = verge?.default_latency_timeout || 10000;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ export const ProxyItem = (props: Props) => {
|
|||||||
|
|
||||||
const onDelay = useLockFn(async () => {
|
const onDelay = useLockFn(async () => {
|
||||||
setDelay(-2);
|
setDelay(-2);
|
||||||
setDelay(await delayManager.checkDelay(proxy.name, groupName));
|
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -149,14 +151,14 @@ export const ProxyItem = (props: Props) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelay();
|
onDelay();
|
||||||
}}
|
}}
|
||||||
color={delayManager.formatDelayColor(delay)}
|
color={delayManager.formatDelayColor(delay, timeout)}
|
||||||
sx={({ palette }) =>
|
sx={({ palette }) =>
|
||||||
!proxy.provider
|
!proxy.provider
|
||||||
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{delayManager.formatDelay(delay)}
|
{delayManager.formatDelay(delay, timeout)}
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type HeadStateStorage = Record<string, Record<string, HeadState>>;
|
|||||||
const HEAD_STATE_KEY = "proxy-head-state";
|
const HEAD_STATE_KEY = "proxy-head-state";
|
||||||
export const DEFAULT_STATE: HeadState = {
|
export const DEFAULT_STATE: HeadState = {
|
||||||
open: false,
|
open: false,
|
||||||
showType: false,
|
showType: true,
|
||||||
sortType: 0,
|
sortType: 0,
|
||||||
filterText: "",
|
filterText: "",
|
||||||
textState: null,
|
textState: null,
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import useSWR from "swr";
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Checkbox, Divider, Stack, Tooltip, Typography } from "@mui/material";
|
|
||||||
import { InfoRounded } from "@mui/icons-material";
|
|
||||||
import { getRuntimeExists } from "@/services/cmds";
|
|
||||||
import {
|
|
||||||
HANDLE_FIELDS,
|
|
||||||
DEFAULT_FIELDS,
|
|
||||||
OTHERS_FIELDS,
|
|
||||||
} from "@/utils/clash-fields";
|
|
||||||
import { BaseDialog, DialogRef } from "@/components/base";
|
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
|
|
||||||
const otherFields = [...OTHERS_FIELDS];
|
|
||||||
const handleFields = [...HANDLE_FIELDS, ...DEFAULT_FIELDS];
|
|
||||||
|
|
||||||
export const ClashFieldViewer = forwardRef<DialogRef>((props, ref) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { profiles = {}, patchProfiles } = useProfiles();
|
|
||||||
const { data: existsKeys = [], mutate: mutateExists } = useSWR(
|
|
||||||
"getRuntimeExists",
|
|
||||||
getRuntimeExists
|
|
||||||
);
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [selected, setSelected] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
open: () => {
|
|
||||||
mutateExists();
|
|
||||||
setSelected(profiles.valid || []);
|
|
||||||
setOpen(true);
|
|
||||||
},
|
|
||||||
close: () => setOpen(false),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const handleChange = (item: string) => {
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
setSelected((old) =>
|
|
||||||
old.includes(item) ? old.filter((e) => e !== item) : [...old, item]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
setOpen(false);
|
|
||||||
|
|
||||||
const oldSet = new Set(profiles.valid || []);
|
|
||||||
const curSet = new Set(selected);
|
|
||||||
const joinSet = new Set(selected.concat([...oldSet]));
|
|
||||||
|
|
||||||
if (curSet.size === oldSet.size && curSet.size === joinSet.size) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await patchProfiles({ valid: [...curSet] });
|
|
||||||
// Notice.success("Refresh clash config", 1000);
|
|
||||||
} catch (err: any) {
|
|
||||||
Notice.error(err?.message || err.toString());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseDialog
|
|
||||||
open={open}
|
|
||||||
title={t("Clash Field")}
|
|
||||||
contentSx={{
|
|
||||||
pb: 0,
|
|
||||||
width: 320,
|
|
||||||
height: 300,
|
|
||||||
overflowY: "auto",
|
|
||||||
userSelect: "text",
|
|
||||||
}}
|
|
||||||
okBtn={t("Save")}
|
|
||||||
cancelBtn={t("Back")}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onCancel={() => setOpen(false)}
|
|
||||||
onOk={handleSave}
|
|
||||||
>
|
|
||||||
{otherFields.map((item) => {
|
|
||||||
const inSelect = selected.includes(item);
|
|
||||||
const inConfig = existsKeys.includes(item);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack key={item} mb={0.5} direction="row" alignItems="center">
|
|
||||||
<Checkbox
|
|
||||||
checked={inSelect}
|
|
||||||
size="small"
|
|
||||||
sx={{ p: 0.5 }}
|
|
||||||
onChange={() => handleChange(item)}
|
|
||||||
/>
|
|
||||||
<Typography width="100%">{item}</Typography>
|
|
||||||
|
|
||||||
{!inSelect && inConfig && <WarnIcon />}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Divider sx={{ my: 1 }}>
|
|
||||||
<Typography color="text.secondary" fontSize={14}>
|
|
||||||
Clash Verge Control Fields
|
|
||||||
</Typography>
|
|
||||||
</Divider>
|
|
||||||
|
|
||||||
{handleFields.map((item) => (
|
|
||||||
<Stack key={item} mb={0.5} direction="row" alignItems="center">
|
|
||||||
<Checkbox defaultChecked disabled size="small" sx={{ p: 0.5 }} />
|
|
||||||
<Typography>{item}</Typography>
|
|
||||||
</Stack>
|
|
||||||
))}
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function WarnIcon() {
|
|
||||||
return (
|
|
||||||
<Tooltip title="The field exists in the config but not enabled.">
|
|
||||||
<InfoRounded color="warning" sx={{ cursor: "pointer", opacity: 0.5 }} />
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -13,26 +13,42 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const { verge, patchVerge } = useVerge();
|
const { verge, patchVerge } = useVerge();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [mixedPort, setMixedPort] = useState(
|
||||||
|
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
|
||||||
|
);
|
||||||
|
const [socksPort, setSocksPort] = useState(
|
||||||
|
verge?.verge_socks_port ?? clashInfo?.socks_port ?? 7898
|
||||||
|
);
|
||||||
const [port, setPort] = useState(
|
const [port, setPort] = useState(
|
||||||
verge?.verge_mixed_port ?? clashInfo?.port ?? 7897
|
verge?.verge_port ?? clashInfo?.port ?? 7899
|
||||||
);
|
);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
if (verge?.verge_mixed_port) setPort(verge?.verge_mixed_port);
|
if (verge?.verge_mixed_port) setMixedPort(verge?.verge_mixed_port);
|
||||||
|
if (verge?.verge_socks_port) setSocksPort(verge?.verge_socks_port);
|
||||||
|
if (verge?.verge_port) setPort(verge?.verge_port);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
},
|
},
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
const onSave = useLockFn(async () => {
|
||||||
if (port === verge?.verge_mixed_port) {
|
if (
|
||||||
|
mixedPort === verge?.verge_mixed_port &&
|
||||||
|
socksPort === verge?.verge_socks_port &&
|
||||||
|
port === verge?.verge_port
|
||||||
|
) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await patchInfo({ "mixed-port": port });
|
await patchInfo({ "mixed-port": mixedPort });
|
||||||
await patchVerge({ verge_mixed_port: port });
|
await patchInfo({ "socks-port": socksPort });
|
||||||
|
await patchInfo({ port });
|
||||||
|
await patchVerge({ verge_mixed_port: mixedPort });
|
||||||
|
await patchVerge({ verge_socks_port: socksPort });
|
||||||
|
await patchVerge({ verge_port: port });
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
Notice.success("Change Clash port successfully!", 1000);
|
Notice.success("Change Clash port successfully!", 1000);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -54,6 +70,30 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
<List>
|
<List>
|
||||||
<ListItem sx={{ padding: "5px 2px" }}>
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
<ListItemText primary="Mixed Port" />
|
<ListItemText primary="Mixed Port" />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
sx={{ width: 135 }}
|
||||||
|
value={mixedPort}
|
||||||
|
onChange={(e) =>
|
||||||
|
setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary="Socks Port" />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
sx={{ width: 135 }}
|
||||||
|
value={socksPort}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSocksPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary="Http Port" />
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const [values, setValues] = useState({
|
const [values, setValues] = useState({
|
||||||
appLogLevel: "info",
|
appLogLevel: "info",
|
||||||
autoCloseConnection: true,
|
autoCloseConnection: true,
|
||||||
enableClashFields: true,
|
|
||||||
enableBuiltinEnhanced: true,
|
enableBuiltinEnhanced: true,
|
||||||
proxyLayoutColumn: 6,
|
proxyLayoutColumn: 6,
|
||||||
defaultLatencyTest: "",
|
defaultLatencyTest: "",
|
||||||
autoLogClean: 0,
|
autoLogClean: 0,
|
||||||
|
defaultLatencyTimeout: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -34,11 +34,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
setValues({
|
setValues({
|
||||||
appLogLevel: verge?.app_log_level ?? "info",
|
appLogLevel: verge?.app_log_level ?? "info",
|
||||||
autoCloseConnection: verge?.auto_close_connection ?? true,
|
autoCloseConnection: verge?.auto_close_connection ?? true,
|
||||||
enableClashFields: verge?.enable_clash_fields ?? true,
|
|
||||||
enableBuiltinEnhanced: verge?.enable_builtin_enhanced ?? true,
|
enableBuiltinEnhanced: verge?.enable_builtin_enhanced ?? true,
|
||||||
proxyLayoutColumn: verge?.proxy_layout_column || 6,
|
proxyLayoutColumn: verge?.proxy_layout_column || 6,
|
||||||
defaultLatencyTest: verge?.default_latency_test || "",
|
defaultLatencyTest: verge?.default_latency_test || "",
|
||||||
autoLogClean: verge?.auto_log_clean || 0,
|
autoLogClean: verge?.auto_log_clean || 0,
|
||||||
|
defaultLatencyTimeout: verge?.default_latency_timeout || 10000,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
@@ -49,10 +49,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
await patchVerge({
|
await patchVerge({
|
||||||
app_log_level: values.appLogLevel,
|
app_log_level: values.appLogLevel,
|
||||||
auto_close_connection: values.autoCloseConnection,
|
auto_close_connection: values.autoCloseConnection,
|
||||||
enable_clash_fields: values.enableClashFields,
|
|
||||||
enable_builtin_enhanced: values.enableBuiltinEnhanced,
|
enable_builtin_enhanced: values.enableBuiltinEnhanced,
|
||||||
proxy_layout_column: values.proxyLayoutColumn,
|
proxy_layout_column: values.proxyLayoutColumn,
|
||||||
default_latency_test: values.defaultLatencyTest,
|
default_latency_test: values.defaultLatencyTest,
|
||||||
|
default_latency_timeout: values.defaultLatencyTimeout,
|
||||||
auto_log_clean: values.autoLogClean as any,
|
auto_log_clean: values.autoLogClean as any,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -105,17 +105,6 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem sx={{ padding: "5px 2px" }}>
|
|
||||||
<ListItemText primary={t("Enable Clash Fields Filter")} />
|
|
||||||
<Switch
|
|
||||||
edge="end"
|
|
||||||
checked={values.enableClashFields}
|
|
||||||
onChange={(_, c) =>
|
|
||||||
setValues((v) => ({ ...v, enableClashFields: c }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<ListItem sx={{ padding: "5px 2px" }}>
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
<ListItemText primary={t("Enable Builtin Enhanced")} />
|
<ListItemText primary={t("Enable Builtin Enhanced")} />
|
||||||
<Switch
|
<Switch
|
||||||
@@ -193,6 +182,27 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Default Latency Timeout")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.defaultLatencyTimeout}
|
||||||
|
placeholder="http://1.1.1.1"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({
|
||||||
|
...v,
|
||||||
|
defaultLatencyTimeout: parseInt(e.target.value),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,15 +11,11 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
Tooltip,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import getSystem from "@/utils/get-system";
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { getSystemProxy } from "@/services/cmds";
|
import { getSystemProxy } from "@/services/cmds";
|
||||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||||
|
|
||||||
const OS = getSystem();
|
|
||||||
|
|
||||||
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -35,14 +31,12 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
enable_proxy_guard,
|
enable_proxy_guard,
|
||||||
system_proxy_bypass,
|
system_proxy_bypass,
|
||||||
proxy_guard_duration,
|
proxy_guard_duration,
|
||||||
system_proxy_registry_mode,
|
|
||||||
} = verge ?? {};
|
} = verge ?? {};
|
||||||
|
|
||||||
const [value, setValue] = useState({
|
const [value, setValue] = useState({
|
||||||
guard: enable_proxy_guard,
|
guard: enable_proxy_guard,
|
||||||
bypass: system_proxy_bypass,
|
bypass: system_proxy_bypass,
|
||||||
duration: proxy_guard_duration ?? 10,
|
duration: proxy_guard_duration ?? 10,
|
||||||
registryMode: system_proxy_registry_mode,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -52,7 +46,6 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
guard: enable_proxy_guard,
|
guard: enable_proxy_guard,
|
||||||
bypass: system_proxy_bypass,
|
bypass: system_proxy_bypass,
|
||||||
duration: proxy_guard_duration ?? 10,
|
duration: proxy_guard_duration ?? 10,
|
||||||
registryMode: system_proxy_registry_mode,
|
|
||||||
});
|
});
|
||||||
getSystemProxy().then((p) => setSysproxy(p));
|
getSystemProxy().then((p) => setSysproxy(p));
|
||||||
},
|
},
|
||||||
@@ -76,9 +69,6 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
if (value.bypass !== system_proxy_bypass) {
|
if (value.bypass !== system_proxy_bypass) {
|
||||||
patch.system_proxy_bypass = value.bypass;
|
patch.system_proxy_bypass = value.bypass;
|
||||||
}
|
}
|
||||||
if (value.registryMode !== system_proxy_registry_mode) {
|
|
||||||
patch.system_proxy_registry_mode = value.registryMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await patchVerge(patch);
|
await patchVerge(patch);
|
||||||
@@ -92,7 +82,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
<BaseDialog
|
<BaseDialog
|
||||||
open={open}
|
open={open}
|
||||||
title={t("System Proxy Setting")}
|
title={t("System Proxy Setting")}
|
||||||
contentSx={{ width: 450, maxHeight: 500 }}
|
contentSx={{ width: 450, maxHeight: 300 }}
|
||||||
okBtn={t("Save")}
|
okBtn={t("Save")}
|
||||||
cancelBtn={t("Cancel")}
|
cancelBtn={t("Cancel")}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
@@ -144,27 +134,6 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{OS === "windows" && (
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
enabled
|
|
||||||
? t("Please disable the system proxy")
|
|
||||||
: t("Using the registry instead of Windows API")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItem sx={{ padding: "5px 2px" }}>
|
|
||||||
<ListItemText primary={t("Use Registry")} />
|
|
||||||
<Switch
|
|
||||||
edge="end"
|
|
||||||
disabled={enabled}
|
|
||||||
checked={value.registryMode}
|
|
||||||
onChange={(_, e) =>
|
|
||||||
setValue((v) => ({ ...v, registryMode: e }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<Box sx={{ mt: 2.5 }}>
|
<Box sx={{ mt: 2.5 }}>
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useClash } from "@/hooks/use-clash";
|
||||||
|
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||||
|
|
||||||
|
export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { clash, mutateClash, patchClash } = useClash();
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [values, setValues] = useState({
|
||||||
|
stack: "gVisor",
|
||||||
|
device: "Mihomo",
|
||||||
|
autoRoute: true,
|
||||||
|
autoDetectInterface: true,
|
||||||
|
dnsHijack: ["any:53", "tcp://any:53"],
|
||||||
|
strictRoute: true,
|
||||||
|
mtu: 9000,
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
open: () => {
|
||||||
|
setOpen(true);
|
||||||
|
setValues({
|
||||||
|
stack: clash?.tun.stack ?? "gVisor",
|
||||||
|
device: clash?.tun.device ?? "Mihomo",
|
||||||
|
autoRoute: clash?.tun["auto-route"] ?? true,
|
||||||
|
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
||||||
|
dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53", "tcp://any:53"],
|
||||||
|
strictRoute: clash?.tun["strict-route"] ?? true,
|
||||||
|
mtu: clash?.tun.mtu ?? 9000,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close: () => setOpen(false),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onSave = useLockFn(async () => {
|
||||||
|
try {
|
||||||
|
let tun = {
|
||||||
|
stack: values.stack,
|
||||||
|
device: values.device,
|
||||||
|
"auto-route": values.autoRoute,
|
||||||
|
"auto-detect-interface": values.autoDetectInterface,
|
||||||
|
"dns-hijack": values.dnsHijack,
|
||||||
|
"strict-route": values.strictRoute,
|
||||||
|
mtu: values.mtu,
|
||||||
|
};
|
||||||
|
await patchClash({ tun });
|
||||||
|
await mutateClash(
|
||||||
|
(old) => ({
|
||||||
|
...(old! || {}),
|
||||||
|
tun,
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
setOpen(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
Notice.error(err.message || err.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
open={open}
|
||||||
|
title={t("Tun Mode")}
|
||||||
|
contentSx={{ width: 450 }}
|
||||||
|
okBtn={t("Save")}
|
||||||
|
cancelBtn={t("Cancel")}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onOk={onSave}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Stack")} />
|
||||||
|
<Select
|
||||||
|
size="small"
|
||||||
|
sx={{ width: 100, "> div": { py: "7.5px" } }}
|
||||||
|
value={values.stack}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValues((v) => ({
|
||||||
|
...v,
|
||||||
|
stack: e.target.value as string,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{["System", "gVisor", "Mixed"].map((i) => (
|
||||||
|
<MenuItem value={i} key={i}>
|
||||||
|
{i}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Device")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.device}
|
||||||
|
placeholder="Mihomo"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({ ...v, device: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Auto Route")} />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={values.autoRoute}
|
||||||
|
onChange={(_, c) => setValues((v) => ({ ...v, autoRoute: c }))}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Strict Route")} />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={values.strictRoute}
|
||||||
|
onChange={(_, c) => setValues((v) => ({ ...v, strictRoute: c }))}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Auto Detect Interface")} />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={values.autoDetectInterface}
|
||||||
|
onChange={(_, c) =>
|
||||||
|
setValues((v) => ({ ...v, autoDetectInterface: c }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("DNS Hijack")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.dnsHijack.join(",")}
|
||||||
|
placeholder="Please use , to separate multiple DNS servers"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({ ...v, dnsHijack: e.target.value.split(",") }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("MTU")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.mtu}
|
||||||
|
placeholder="9000"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({
|
||||||
|
...v,
|
||||||
|
mtu: parseInt(e.target.value),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -15,7 +15,6 @@ import { DialogRef, Notice } from "@/components/base";
|
|||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
import { WebUIViewer } from "./mods/web-ui-viewer";
|
||||||
import { ClashFieldViewer } from "./mods/clash-field-viewer";
|
|
||||||
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
||||||
import { ControllerViewer } from "./mods/controller-viewer";
|
import { ControllerViewer } from "./mods/controller-viewer";
|
||||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||||
@@ -39,14 +38,9 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const { ipv6, "allow-lan": allowLan, "log-level": logLevel } = clash ?? {};
|
const { ipv6, "allow-lan": allowLan, "log-level": logLevel } = clash ?? {};
|
||||||
|
|
||||||
const {
|
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
||||||
enable_random_port = false,
|
|
||||||
verge_mixed_port,
|
|
||||||
enable_clash_fields = true,
|
|
||||||
} = verge ?? {};
|
|
||||||
|
|
||||||
const webRef = useRef<DialogRef>(null);
|
const webRef = useRef<DialogRef>(null);
|
||||||
const fieldRef = useRef<DialogRef>(null);
|
|
||||||
const portRef = useRef<DialogRef>(null);
|
const portRef = useRef<DialogRef>(null);
|
||||||
const ctrlRef = useRef<DialogRef>(null);
|
const ctrlRef = useRef<DialogRef>(null);
|
||||||
const coreRef = useRef<DialogRef>(null);
|
const coreRef = useRef<DialogRef>(null);
|
||||||
@@ -70,7 +64,6 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<SettingList title={t("Clash Setting")}>
|
<SettingList title={t("Clash Setting")}>
|
||||||
<WebUIViewer ref={webRef} />
|
<WebUIViewer ref={webRef} />
|
||||||
<ClashFieldViewer ref={fieldRef} />
|
|
||||||
<ClashPortViewer ref={portRef} />
|
<ClashPortViewer ref={portRef} />
|
||||||
<ControllerViewer ref={ctrlRef} />
|
<ControllerViewer ref={ctrlRef} />
|
||||||
<ClashCoreViewer ref={coreRef} />
|
<ClashCoreViewer ref={coreRef} />
|
||||||
@@ -121,7 +114,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
label={t("Mixed Port")}
|
label={t("Port Config")}
|
||||||
extra={
|
extra={
|
||||||
<Tooltip title={t("Random Port")}>
|
<Tooltip title={t("Random Port")}>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -176,19 +169,6 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
{enable_clash_fields && (
|
|
||||||
<SettingItem label={t("Clash Field")}>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
sx={{ my: "2px" }}
|
|
||||||
onClick={() => fieldRef.current?.open()}
|
|
||||||
>
|
|
||||||
<ArrowForward />
|
|
||||||
</IconButton>
|
|
||||||
</SettingItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
label={t("Clash Core")}
|
label={t("Clash Core")}
|
||||||
extra={
|
extra={
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { IconButton, Switch } from "@mui/material";
|
import { IconButton, Switch, Tooltip } from "@mui/material";
|
||||||
import { ArrowForward, PrivacyTipRounded, Settings } from "@mui/icons-material";
|
import { PrivacyTipRounded, Settings, InfoRounded } from "@mui/icons-material";
|
||||||
import { checkService } from "@/services/cmds";
|
import { checkService } from "@/services/cmds";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { DialogRef } from "@/components/base";
|
import { DialogRef } from "@/components/base";
|
||||||
@@ -10,6 +10,7 @@ import { SettingList, SettingItem } from "./mods/setting-comp";
|
|||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { ServiceViewer } from "./mods/service-viewer";
|
import { ServiceViewer } from "./mods/service-viewer";
|
||||||
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
||||||
|
import { TunViewer } from "./mods/tun-viewer";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -36,6 +37,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const serviceRef = useRef<DialogRef>(null);
|
const serviceRef = useRef<DialogRef>(null);
|
||||||
const sysproxyRef = useRef<DialogRef>(null);
|
const sysproxyRef = useRef<DialogRef>(null);
|
||||||
|
const tunRef = useRef<DialogRef>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
enable_tun_mode,
|
enable_tun_mode,
|
||||||
@@ -53,11 +55,41 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<SettingList title={t("System Setting")}>
|
<SettingList title={t("System Setting")}>
|
||||||
<SysproxyViewer ref={sysproxyRef} />
|
<SysproxyViewer ref={sysproxyRef} />
|
||||||
|
<TunViewer ref={tunRef} />
|
||||||
{isWIN && (
|
{isWIN && (
|
||||||
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingItem label={t("Tun Mode")}>
|
<SettingItem
|
||||||
|
label={t("Tun Mode")}
|
||||||
|
extra={
|
||||||
|
<>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")
|
||||||
|
}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconButton color="inherit" size="small">
|
||||||
|
<InfoRounded
|
||||||
|
fontSize="inherit"
|
||||||
|
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => tunRef.current?.open()}
|
||||||
|
>
|
||||||
|
<Settings
|
||||||
|
fontSize="inherit"
|
||||||
|
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_tun_mode ?? false}
|
value={enable_tun_mode ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
|
import { exitApp, openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
|
||||||
import { ArrowForward } from "@mui/icons-material";
|
import { ArrowForward } from "@mui/icons-material";
|
||||||
import { checkUpdate } from "@tauri-apps/api/updater";
|
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||||
import { exit } from "@tauri-apps/api/process";
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { version } from "@root/package.json";
|
import { version } from "@root/package.json";
|
||||||
import { DialogRef, Notice } from "@/components/base";
|
import { DialogRef, Notice } from "@/components/base";
|
||||||
@@ -310,7 +309,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
size="small"
|
size="small"
|
||||||
sx={{ my: "2px" }}
|
sx={{ my: "2px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
exit(0);
|
exitApp();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowForward />
|
<ArrowForward />
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const TestItem = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onDelay();
|
|
||||||
listenTsetEvent();
|
listenTsetEvent();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
+26
-1
@@ -48,11 +48,16 @@ export const useClashInfo = () => {
|
|||||||
|
|
||||||
const patchInfo = async (
|
const patchInfo = async (
|
||||||
patch: Partial<
|
patch: Partial<
|
||||||
Pick<IConfigData, "mixed-port" | "external-controller" | "secret">
|
Pick<
|
||||||
|
IConfigData,
|
||||||
|
"port" | "socks-port" | "mixed-port" | "external-controller" | "secret"
|
||||||
|
>
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
const hasInfo =
|
const hasInfo =
|
||||||
patch["mixed-port"] != null ||
|
patch["mixed-port"] != null ||
|
||||||
|
patch["socks-port"] != null ||
|
||||||
|
patch["port"] != null ||
|
||||||
patch["external-controller"] != null ||
|
patch["external-controller"] != null ||
|
||||||
patch.secret != null;
|
patch.secret != null;
|
||||||
|
|
||||||
@@ -68,6 +73,26 @@ export const useClashInfo = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (patch["socks-port"]) {
|
||||||
|
const port = patch["socks-port"];
|
||||||
|
if (port < 1000) {
|
||||||
|
throw new Error("The port should not < 1000");
|
||||||
|
}
|
||||||
|
if (port > 65536) {
|
||||||
|
throw new Error("The port should not > 65536");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patch["port"]) {
|
||||||
|
const port = patch["port"];
|
||||||
|
if (port < 1000) {
|
||||||
|
throw new Error("The port should not < 1000");
|
||||||
|
}
|
||||||
|
if (port > 65536) {
|
||||||
|
throw new Error("The port should not > 65536");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await patchClashConfig(patch);
|
await patchClashConfig(patch);
|
||||||
mutateInfo();
|
mutateInfo();
|
||||||
mutate("getClashConfig");
|
mutate("getClashConfig");
|
||||||
|
|||||||
+4
-4
@@ -75,7 +75,7 @@
|
|||||||
"Allow Lan": "Allow Lan",
|
"Allow Lan": "Allow Lan",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
"Log Level": "Log Level",
|
"Log Level": "Log Level",
|
||||||
"Mixed Port": "Mixed Port",
|
"Port Config": "Port Config",
|
||||||
"Random Port": "Random Port",
|
"Random Port": "Random Port",
|
||||||
"After restart to take effect": "After restart to take effect",
|
"After restart to take effect": "After restart to take effect",
|
||||||
"External": "External",
|
"External": "External",
|
||||||
@@ -93,7 +93,6 @@
|
|||||||
"Proxy Guard": "Proxy Guard",
|
"Proxy Guard": "Proxy Guard",
|
||||||
"Guard Duration": "Guard Duration",
|
"Guard Duration": "Guard Duration",
|
||||||
"Proxy Bypass": "Proxy Bypass",
|
"Proxy Bypass": "Proxy Bypass",
|
||||||
"Use Registry": "Use Registry",
|
|
||||||
"Enable status": "Enable status",
|
"Enable status": "Enable status",
|
||||||
"Server Addr": "Server Addr",
|
"Server Addr": "Server Addr",
|
||||||
"Bypass": "Bypass",
|
"Bypass": "Bypass",
|
||||||
@@ -153,6 +152,7 @@
|
|||||||
"Enable Builtin Enhanced": "Enable Builtin Enhanced",
|
"Enable Builtin Enhanced": "Enable Builtin Enhanced",
|
||||||
"Proxy Layout Column": "Proxy Layout Column",
|
"Proxy Layout Column": "Proxy Layout Column",
|
||||||
"Default Latency Test": "Default Latency Test",
|
"Default Latency Test": "Default Latency Test",
|
||||||
|
"Defaule Latency Timeout": "Defaule Latency Timeout",
|
||||||
|
|
||||||
"Auto Log Clean": "Auto Log Clean",
|
"Auto Log Clean": "Auto Log Clean",
|
||||||
"Never Clean": "Never Clean",
|
"Never Clean": "Never Clean",
|
||||||
@@ -161,6 +161,6 @@
|
|||||||
"Retain 90 Days": "Retain 90 Days",
|
"Retain 90 Days": "Retain 90 Days",
|
||||||
|
|
||||||
"Portable Updater Error": "The portable version does not support in-app updates. Please manually download and replace it",
|
"Portable Updater Error": "The portable version does not support in-app updates. Please manually download and replace it",
|
||||||
"Please disable the system proxy": "Please disable the system proxy",
|
"Tun Mode Info Windows": "The Tun mode requires granting core-related permissions. Please enable service mode before using it",
|
||||||
"Using the registry instead of Windows API": "Using the registry instead of Windows API"
|
"Tun Mode Info Unix": "The Tun mode requires granting core-related permissions. Before using it, please authorize the core in the core settings"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-5
@@ -73,7 +73,7 @@
|
|||||||
"Allow Lan": "Разрешить локальную сеть",
|
"Allow Lan": "Разрешить локальную сеть",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
"Log Level": "Уровень логов",
|
"Log Level": "Уровень логов",
|
||||||
"Mixed Port": "Смешанный порт",
|
"Port Config": "Настройка порта",
|
||||||
"Random Port": "Случайный порт",
|
"Random Port": "Случайный порт",
|
||||||
"After restart to take effect": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
|
"After restart to take effect": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
|
||||||
"Clash Core": "Ядро Clash",
|
"Clash Core": "Ядро Clash",
|
||||||
@@ -88,7 +88,6 @@
|
|||||||
"Proxy Guard": "Защита прокси",
|
"Proxy Guard": "Защита прокси",
|
||||||
"Guard Duration": "Период защиты",
|
"Guard Duration": "Период защиты",
|
||||||
"Proxy Bypass": "Игнорирование прокси",
|
"Proxy Bypass": "Игнорирование прокси",
|
||||||
"Use Registry": "Использование реестра",
|
|
||||||
"Current System Proxy": "Текущий системный прокси",
|
"Current System Proxy": "Текущий системный прокси",
|
||||||
"Theme Mode": "Режим темы",
|
"Theme Mode": "Режим темы",
|
||||||
"Tray Click Event": "Событие щелчка в лотке",
|
"Tray Click Event": "Событие щелчка в лотке",
|
||||||
@@ -131,7 +130,5 @@
|
|||||||
"enable_tun_mode": "Включить режим туннеля",
|
"enable_tun_mode": "Включить режим туннеля",
|
||||||
"disable_tun_mode": "Отключить режим туннеля",
|
"disable_tun_mode": "Отключить режим туннеля",
|
||||||
|
|
||||||
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените вручную",
|
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените вручную"
|
||||||
"Please disable the system proxy": "Пожалуйста, отключите системный прокси",
|
|
||||||
"Using the registry instead of Windows API": "Использование реестра вместо Windows API"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -75,7 +75,7 @@
|
|||||||
"Allow Lan": "局域网连接",
|
"Allow Lan": "局域网连接",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
"Log Level": "日志等级",
|
"Log Level": "日志等级",
|
||||||
"Mixed Port": "端口设置",
|
"Port Config": "端口设置",
|
||||||
"Random Port": "随机端口",
|
"Random Port": "随机端口",
|
||||||
"After restart to take effect": "重启后生效",
|
"After restart to take effect": "重启后生效",
|
||||||
"External": "外部控制",
|
"External": "外部控制",
|
||||||
@@ -93,7 +93,6 @@
|
|||||||
"Proxy Guard": "系统代理守卫",
|
"Proxy Guard": "系统代理守卫",
|
||||||
"Guard Duration": "代理守卫间隔",
|
"Guard Duration": "代理守卫间隔",
|
||||||
"Proxy Bypass": "代理绕过",
|
"Proxy Bypass": "代理绕过",
|
||||||
"Use Registry": "使用注册表",
|
|
||||||
"Current System Proxy": "当前系统代理",
|
"Current System Proxy": "当前系统代理",
|
||||||
"Enable status": "开启状态:",
|
"Enable status": "开启状态:",
|
||||||
"Server Addr": "服务地址:",
|
"Server Addr": "服务地址:",
|
||||||
@@ -153,6 +152,7 @@
|
|||||||
"Enable Builtin Enhanced": "开启内建增强功能",
|
"Enable Builtin Enhanced": "开启内建增强功能",
|
||||||
"Proxy Layout Column": "代理页布局列数",
|
"Proxy Layout Column": "代理页布局列数",
|
||||||
"Default Latency Test": "默认测试链接",
|
"Default Latency Test": "默认测试链接",
|
||||||
|
"Default Latency Timeout": "测试超时时间",
|
||||||
|
|
||||||
"Auto Log Clean": "自动清理日志",
|
"Auto Log Clean": "自动清理日志",
|
||||||
"Never Clean": "不清理",
|
"Never Clean": "不清理",
|
||||||
@@ -161,6 +161,6 @@
|
|||||||
"Retain 90 Days": "保留90天",
|
"Retain 90 Days": "保留90天",
|
||||||
|
|
||||||
"Portable Updater Error": "便携版不支持应用内更新,请手动下载替换",
|
"Portable Updater Error": "便携版不支持应用内更新,请手动下载替换",
|
||||||
"Please disable the system proxy": "请先关闭系统代理",
|
"Tun Mode Info Windows": "Tun模式需要授予内核相关权限,使用前请先开启服务模式",
|
||||||
"Using the registry instead of Windows API": "使用注册表替代Windows API"
|
"Tun Mode Info Unix": "Tun模式需要授予内核相关权限,使用前请先在内核设置中给内核授权"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ export const routers = [
|
|||||||
link: "/",
|
link: "/",
|
||||||
ele: ProxiesPage,
|
ele: ProxiesPage,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Label-Test",
|
|
||||||
link: "/test",
|
|
||||||
ele: TestPage,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Label-Profiles",
|
label: "Label-Profiles",
|
||||||
link: "/profile",
|
link: "/profile",
|
||||||
@@ -37,6 +32,11 @@ export const routers = [
|
|||||||
link: "/logs",
|
link: "/logs",
|
||||||
ele: LogsPage,
|
ele: LogsPage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Label-Test",
|
||||||
|
link: "/test",
|
||||||
|
ele: TestPage,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Label-Settings",
|
label: "Label-Settings",
|
||||||
link: "/settings",
|
link: "/settings",
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ const ConnectionsPage = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
mr: 1,
|
mr: 1,
|
||||||
width: i18n.language === "en" ? 190 : 120,
|
width: i18n.language === "en" ? 190 : 120,
|
||||||
|
height: 33.375,
|
||||||
'[role="button"]': { py: 0.65 },
|
'[role="button"]': { py: 0.65 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -193,6 +194,7 @@ const ConnectionsPage = () => {
|
|||||||
placeholder={t("Filter conditions")}
|
placeholder={t("Filter conditions")}
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={(e) => setFilterText(e.target.value)}
|
onChange={(e) => setFilterText(e.target.value)}
|
||||||
|
sx={{ input: { py: 0.65, px: 1.25 } }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -80,7 +80,12 @@ const LogPage = () => {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={logState}
|
value={logState}
|
||||||
onChange={(e) => setLogState(e.target.value)}
|
onChange={(e) => setLogState(e.target.value)}
|
||||||
sx={{ width: 120, mr: 1, '[role="button"]': { py: 0.65 } }}
|
sx={{
|
||||||
|
width: 120,
|
||||||
|
height: 33.375,
|
||||||
|
mr: 1,
|
||||||
|
'[role="button"]': { py: 0.65 },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="all">ALL</MenuItem>
|
<MenuItem value="all">ALL</MenuItem>
|
||||||
<MenuItem value="inf">INFO</MenuItem>
|
<MenuItem value="inf">INFO</MenuItem>
|
||||||
|
|||||||
+13
-11
@@ -107,7 +107,6 @@ export const getProxies = async () => {
|
|||||||
getProxiesInner(),
|
getProxiesInner(),
|
||||||
getProxyProviders(),
|
getProxyProviders(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// provider name map
|
// provider name map
|
||||||
const providerMap = Object.fromEntries(
|
const providerMap = Object.fromEntries(
|
||||||
Object.entries(providerRecord).flatMap(([provider, item]) =>
|
Object.entries(providerRecord).flatMap(([provider, item]) =>
|
||||||
@@ -131,24 +130,27 @@ export const getProxies = async () => {
|
|||||||
|
|
||||||
const { GLOBAL: global, DIRECT: direct, REJECT: reject } = proxyRecord;
|
const { GLOBAL: global, DIRECT: direct, REJECT: reject } = proxyRecord;
|
||||||
|
|
||||||
let groups: IProxyGroupItem[] = [];
|
let groups = Object.values(proxyRecord)
|
||||||
|
.filter((each) => each.name !== "GLOBAL" && each.all)
|
||||||
|
.map((each) => ({
|
||||||
|
...each,
|
||||||
|
all: each.all!.map((item) => generateItem(item)),
|
||||||
|
}));
|
||||||
|
|
||||||
if (global?.all) {
|
if (global?.all) {
|
||||||
groups = global.all
|
let globalGroups = global.all
|
||||||
.filter((name) => proxyRecord[name]?.all)
|
.filter((name) => proxyRecord[name]?.all)
|
||||||
.map((name) => proxyRecord[name])
|
.map((name) => proxyRecord[name])
|
||||||
.map((each) => ({
|
.map((each) => ({
|
||||||
...each,
|
...each,
|
||||||
all: each.all!.map((item) => generateItem(item)),
|
all: each.all!.map((item) => generateItem(item)),
|
||||||
}));
|
}));
|
||||||
} else {
|
let globalNames = globalGroups.map((each) => each.name);
|
||||||
groups = Object.values(proxyRecord)
|
groups = groups
|
||||||
.filter((each) => each.name !== "GLOBAL" && each.all)
|
.filter((group) => {
|
||||||
.map((each) => ({
|
return !globalNames.includes(group.name);
|
||||||
...each,
|
})
|
||||||
all: each.all!.map((item) => generateItem(item)),
|
.concat(globalGroups);
|
||||||
}))
|
|
||||||
.sort((a, b) => b.name.localeCompare(a.name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxies = [direct, reject].concat(
|
const proxies = [direct, reject].concat(
|
||||||
|
|||||||
+14
-2
@@ -160,9 +160,17 @@ export async function openWebUrl(url: string) {
|
|||||||
return invoke<void>("open_web_url", { url });
|
return invoke<void>("open_web_url", { url });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cmdGetProxyDelay(name: string, url?: string) {
|
export async function cmdGetProxyDelay(
|
||||||
|
name: string,
|
||||||
|
timeout: number,
|
||||||
|
url?: string
|
||||||
|
) {
|
||||||
name = encodeURIComponent(name);
|
name = encodeURIComponent(name);
|
||||||
return invoke<{ delay: number }>("clash_api_get_proxy_delay", { name, url });
|
return invoke<{ delay: number }>("clash_api_get_proxy_delay", {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
timeout,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cmdTestDelay(url: string) {
|
export async function cmdTestDelay(url: string) {
|
||||||
@@ -199,3 +207,7 @@ export async function invoke_uwp_tool() {
|
|||||||
export async function getPortableFlag() {
|
export async function getPortableFlag() {
|
||||||
return invoke<boolean>("get_portable_flag");
|
return invoke<boolean>("get_portable_flag");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function exitApp() {
|
||||||
|
return invoke("exit_app");
|
||||||
|
}
|
||||||
|
|||||||
+14
-9
@@ -69,12 +69,12 @@ class DelayManager {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkDelay(name: string, group: string) {
|
async checkDelay(name: string, group: string, timeout: number) {
|
||||||
let delay = -1;
|
let delay = -1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = this.getUrl(group);
|
const url = this.getUrl(group);
|
||||||
const result = await cmdGetProxyDelay(name, url);
|
const result = await cmdGetProxyDelay(name, timeout, url);
|
||||||
delay = result.delay;
|
delay = result.delay;
|
||||||
} catch {
|
} catch {
|
||||||
delay = 1e6; // error
|
delay = 1e6; // error
|
||||||
@@ -84,7 +84,12 @@ class DelayManager {
|
|||||||
return delay;
|
return delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkListDelay(nameList: string[], group: string, concurrency = 36) {
|
async checkListDelay(
|
||||||
|
nameList: string[],
|
||||||
|
group: string,
|
||||||
|
timeout: number,
|
||||||
|
concurrency = 36
|
||||||
|
) {
|
||||||
const names = nameList.filter(Boolean);
|
const names = nameList.filter(Boolean);
|
||||||
// 设置正在延迟测试中
|
// 设置正在延迟测试中
|
||||||
names.forEach((name) => this.setDelay(name, group, -2));
|
names.forEach((name) => this.setDelay(name, group, -2));
|
||||||
@@ -98,7 +103,7 @@ class DelayManager {
|
|||||||
const task = names.shift();
|
const task = names.shift();
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
current += 1;
|
current += 1;
|
||||||
await this.checkDelay(task, group);
|
await this.checkDelay(task, group, timeout);
|
||||||
current -= 1;
|
current -= 1;
|
||||||
total -= 1;
|
total -= 1;
|
||||||
if (total <= 0) resolve(null);
|
if (total <= 0) resolve(null);
|
||||||
@@ -108,15 +113,15 @@ class DelayManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
formatDelay(delay: number) {
|
formatDelay(delay: number, timeout = 10000) {
|
||||||
if (delay <= 0) return "Error";
|
if (delay <= 0) return "Error";
|
||||||
if (delay > 1e5) return "Error";
|
if (delay > 1e5) return "Error";
|
||||||
if (delay >= 10000) return "Timeout"; // 10s
|
if (delay >= timeout) return "Timeout"; // 10s
|
||||||
return `${delay} ms`;
|
return `${delay}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatDelayColor(delay: number) {
|
formatDelayColor(delay: number, timeout = 10000) {
|
||||||
if (delay >= 10000) return "error.main";
|
if (delay >= timeout) return "error.main";
|
||||||
if (delay <= 0) return "error.main";
|
if (delay <= 0) return "error.main";
|
||||||
if (delay > 500) return "warning.main";
|
if (delay > 500) return "warning.main";
|
||||||
return "success.main";
|
return "success.main";
|
||||||
|
|||||||
Vendored
+15
-3
@@ -32,6 +32,15 @@ interface IConfigData {
|
|||||||
"tproxy-port": number;
|
"tproxy-port": number;
|
||||||
"external-controller": string;
|
"external-controller": string;
|
||||||
secret: string;
|
secret: string;
|
||||||
|
tun: {
|
||||||
|
stack: string;
|
||||||
|
device: string;
|
||||||
|
"auto-route": boolean;
|
||||||
|
"auto-detect-interface": boolean;
|
||||||
|
"dns-hijack": string[];
|
||||||
|
"strict-route": boolean;
|
||||||
|
mtu: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRuleItem {
|
interface IRuleItem {
|
||||||
@@ -131,7 +140,9 @@ interface IConnections {
|
|||||||
|
|
||||||
interface IClashInfo {
|
interface IClashInfo {
|
||||||
// status: string;
|
// status: string;
|
||||||
port?: number; // clash mixed port
|
mixed_port?: number; // clash mixed port
|
||||||
|
socks_port?: number; // clash socks port
|
||||||
|
port?: number; // clash http port
|
||||||
server?: string; // external-controller
|
server?: string; // external-controller
|
||||||
secret?: string;
|
secret?: string;
|
||||||
}
|
}
|
||||||
@@ -196,10 +207,11 @@ interface IVergeConfig {
|
|||||||
enable_system_proxy?: boolean;
|
enable_system_proxy?: boolean;
|
||||||
enable_random_port?: boolean;
|
enable_random_port?: boolean;
|
||||||
verge_mixed_port?: number;
|
verge_mixed_port?: number;
|
||||||
|
verge_socks_port?: number;
|
||||||
|
verge_port?: number;
|
||||||
enable_proxy_guard?: boolean;
|
enable_proxy_guard?: boolean;
|
||||||
proxy_guard_duration?: number;
|
proxy_guard_duration?: number;
|
||||||
system_proxy_bypass?: string;
|
system_proxy_bypass?: string;
|
||||||
system_proxy_registry_mode?: boolean;
|
|
||||||
web_ui_list?: string[];
|
web_ui_list?: string[];
|
||||||
hotkeys?: string[];
|
hotkeys?: string[];
|
||||||
theme_setting?: {
|
theme_setting?: {
|
||||||
@@ -216,7 +228,7 @@ interface IVergeConfig {
|
|||||||
};
|
};
|
||||||
auto_close_connection?: boolean;
|
auto_close_connection?: boolean;
|
||||||
default_latency_test?: string;
|
default_latency_test?: string;
|
||||||
enable_clash_fields?: boolean;
|
default_latency_timeout?: number;
|
||||||
enable_builtin_enhanced?: boolean;
|
enable_builtin_enhanced?: boolean;
|
||||||
auto_log_clean?: 0 | 1 | 2 | 3;
|
auto_log_clean?: 0 | 1 | 2 | 3;
|
||||||
proxy_layout_column?: number;
|
proxy_layout_column?: number;
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
export const HANDLE_FIELDS = [
|
|
||||||
"mode",
|
|
||||||
"port",
|
|
||||||
"socks-port",
|
|
||||||
"mixed-port",
|
|
||||||
"allow-lan",
|
|
||||||
"log-level",
|
|
||||||
"ipv6",
|
|
||||||
"secret",
|
|
||||||
"external-controller",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DEFAULT_FIELDS = [
|
|
||||||
"proxies",
|
|
||||||
"proxy-groups",
|
|
||||||
"proxy-providers",
|
|
||||||
"rules",
|
|
||||||
"rule-providers",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const OTHERS_FIELDS = [
|
|
||||||
"dns",
|
|
||||||
"tun",
|
|
||||||
"ebpf",
|
|
||||||
"hosts",
|
|
||||||
"script",
|
|
||||||
"profile",
|
|
||||||
"payload",
|
|
||||||
"tunnels",
|
|
||||||
"auto-redir",
|
|
||||||
"experimental",
|
|
||||||
"interface-name",
|
|
||||||
"routing-mark",
|
|
||||||
"redir-port",
|
|
||||||
"tproxy-port",
|
|
||||||
"iptables",
|
|
||||||
"external-ui",
|
|
||||||
"bind-address",
|
|
||||||
"authentication",
|
|
||||||
"tls", // meta
|
|
||||||
"sniffer", // meta
|
|
||||||
"geox-url", // meta
|
|
||||||
"listeners", // meta
|
|
||||||
"sub-rules", // meta
|
|
||||||
"geodata-mode", // meta
|
|
||||||
"unified-delay", // meta
|
|
||||||
"tcp-concurrent", // meta
|
|
||||||
"enable-process", // meta
|
|
||||||
"find-process-mode", // meta
|
|
||||||
"skip-auth-prefixes", // meta
|
|
||||||
"external-controller-tls", // meta
|
|
||||||
"global-client-fingerprint", // meta
|
|
||||||
] as const;
|
|
||||||
Reference in New Issue
Block a user