Compare commits
281 Commits
55
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,23 +1,41 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
name: 问题反馈 / Bug report
|
||||
title: "[BUG] "
|
||||
description: 反馈你遇到的问题 / Report the issue you are experiencing
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) and [FAQ](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the problem still exists
|
||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 / Describe the bug
|
||||
description: 详细清晰地描述你遇到的问题 / A clear and concise description of what the bug is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
label: 复现步骤 / To Reproduce
|
||||
description: 请提供复现问题的步骤 / Steps to reproduce the behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Platform
|
||||
label: 操作系统 / OS
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
@@ -26,20 +44,13 @@ body:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: System Version
|
||||
placeholder: "e.g. macOS 10.15.7"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Software Version
|
||||
placeholder: "e.g. 1.4.3"
|
||||
label: 操作系统版本 / OS Version
|
||||
description: 请提供你的操作系统版本,Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log
|
||||
description: "Log file content or screenshot"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
label: 日志 / Log
|
||||
description: 请提供完整或相关部分的Debug日志 / Please provide the complete or relevant part of the Debug log
|
||||
validations:
|
||||
required: true
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: 讨论交流 / Communication
|
||||
url: https://t.me/clash_verge_rev
|
||||
about: 在 Telegram 群组中与其他用户讨论交流 / Communicate with other users in the Telegram group
|
||||
44
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,27 +1,35 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature]"
|
||||
name: 功能请求 / Feature request
|
||||
title: "[Feature] "
|
||||
description: 提出你的功能请求 / Propose your feature request
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 确认软件不存在类似的功能
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) to confirm that the software does not have similar functions
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
|
||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述 / Feature description
|
||||
description: 详细清晰地描述你的功能请求 / A clear and concise description of what the feature is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
label: 使用场景 / Use case
|
||||
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
|
||||
7
.github/build-for-linux/build.sh
vendored
@@ -1,9 +1,8 @@
|
||||
# pnpm install --resolution-only
|
||||
pnpm install
|
||||
pnpm check $INPUT_TARGET
|
||||
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
|
||||
pnpm build --target $INPUT_TARGET
|
||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||
cargo tauri build --target $INPUT_TARGET
|
||||
else
|
||||
pnpm build --target $INPUT_TARGET -b deb
|
||||
cargo tauri build --target $INPUT_TARGET -b deb,rpm
|
||||
fi
|
||||
|
||||
20
.github/build-for-linux/entrypoint.sh
vendored
@@ -5,6 +5,8 @@ tar -Jxvf ./node-v20.10.0-linux-x64.tar.xz
|
||||
export PATH=$(pwd)/node-v20.10.0-linux-x64/bin:$PATH
|
||||
npm install pnpm -g
|
||||
|
||||
cargo install --git https://github.com/tauri-apps/tauri --branch 1.x tauri-cli
|
||||
|
||||
rustup target add "$INPUT_TARGET"
|
||||
|
||||
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
|
||||
@@ -17,29 +19,35 @@ elif [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then
|
||||
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
|
||||
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
|
||||
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 --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libssl-dev:arm64 libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
||||
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
|
||||
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
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
|
||||
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 --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libssl-dev:armhf libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
|
||||
export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
|
||||
export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
elif [ "$INPUT_TARGET" = "riscv64gc-unknown-linux-gnu" ]; then
|
||||
dpkg --add-architecture riscv64
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:riscv64 libtinfo6:riscv64 linux-libc-dev:riscv64 libncursesw6:riscv64 libssl3:riscv64 libcups2:riscv64
|
||||
apt-get install -y --no-install-recommends g++-riscv64-linux-gnu libc6-dev-riscv64-cross libwebkit2gtk-4.0-dev:riscv64 libgtk-3-dev:riscv64 patchelf:riscv64 librsvg2-dev:riscv64 libayatana-appindicator3-dev:riscv64
|
||||
export CARGO_TARGET_RISCV64_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
|
||||
export CC_riscv64_unknown_linux_gnu=riscv64-linux-gnu-gcc
|
||||
export CXX_riscv64_unknown_linux_gnu=riscv64-linux-gnu-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/riscv64-linux-gnu/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
else
|
||||
echo "Unknown target: $INPUT_TARGET" && exit 1
|
||||
fi
|
||||
|
||||
287
.github/workflows/alpha.yml
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
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: i686-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: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@1.77.0
|
||||
|
||||
- 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@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9
|
||||
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:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
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 }}
|
||||
|
||||
alpha-for-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: i686-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build for Linux
|
||||
uses: ./.github/build-for-linux
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
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
|
||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Release
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v2
|
||||
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@v2
|
||||
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
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||
|
||||
alpha-for-fixed-webview2:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
arch: x64
|
||||
- os: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
arch: x86
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: Download WebView2 Runtime
|
||||
run: |
|
||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
|
||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
|
||||
Remove-Item .\src-tauri\tauri.windows.conf.json
|
||||
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
|
||||
|
||||
- name: Tauri build
|
||||
id: build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tauriScript: pnpm
|
||||
args: --target ${{ matrix.target }}
|
||||
|
||||
- name: Rename
|
||||
run: |
|
||||
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.exe' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.exe'
|
||||
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip'
|
||||
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip.sig' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip.sig'
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
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/nsis/*setup*
|
||||
|
||||
- name: Portable Bundle
|
||||
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
update_tag:
|
||||
name: Update tag
|
||||
runs-on: ubuntu-latest
|
||||
needs: [alpha, alpha-for-linux, alpha-for-fixed-webview2]
|
||||
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'
|
||||
## 我应该下载哪个版本?
|
||||
|
||||
### MacOS (提示文件损坏或开发者无法验证请查看下面FAQ)
|
||||
- MacOS intel芯片: x64.dmg
|
||||
- MacOS apple M芯片: aarch64.dmg
|
||||
|
||||
### Linux
|
||||
- Linux 64位: amd64.AppImage/amd64.deb/amd64.rpm
|
||||
- Linux 32位: i386.deb/i386.rpm
|
||||
- Linux arm64架构: arm64.deb/aarch64.rpm
|
||||
- Linux armv7架构: armhf.deb/armhfp.rpm
|
||||
|
||||
### Windows (Win7 用户请查看下面FAQ中的解决方案)
|
||||
#### 正常版本(推荐)
|
||||
- 64位: x64-setup.exe
|
||||
- 32位: x86-setup.exe
|
||||
- arm64架构: arm64-setup.exe
|
||||
#### 便携版(不推荐使用,无法自动更新)
|
||||
- 64位: x64_portable.zip
|
||||
- 32位: x86_portable.zip
|
||||
- arm64架构: arm64_portable.zip
|
||||
#### 内置Webview2版(体积较大,仅在企业版系统或Win7无法安装webview2时使用)
|
||||
- 64位: x64_fixed_webview2-setup.exe
|
||||
- 32位: x86_fixed_webview2-setup.exe
|
||||
- arm64架构: arm64_fixed_webview2-setup.exe
|
||||
|
||||
### FAQ
|
||||
|
||||
- [FAQ](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
generate_release_notes: true
|
||||
162
.github/workflows/release.yml
vendored
@@ -2,9 +2,6 @@ name: Release Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v**
|
||||
permissions: write-all
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
@@ -32,19 +29,8 @@ jobs:
|
||||
- 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
|
||||
uses: dtolnay/rust-toolchain@1.77.0
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
@@ -59,10 +45,10 @@ jobs:
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -73,15 +59,14 @@ jobs:
|
||||
- name: Tauri build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tagName: v__VERSION__
|
||||
releaseName: "Clash Verge v__VERSION__"
|
||||
releaseName: "Clash Verge Rev v__VERSION__"
|
||||
releaseBody: "More new features are now supported."
|
||||
releaseDraft: false
|
||||
prerelease: false
|
||||
tauriScript: pnpm
|
||||
args: --target ${{ matrix.target }}
|
||||
|
||||
@@ -90,8 +75,6 @@ jobs:
|
||||
run: pnpm portable ${{ matrix.target }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
release-for-linux:
|
||||
strategy:
|
||||
@@ -104,8 +87,8 @@ jobs:
|
||||
target: i686-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
# - os: ubuntu-latest
|
||||
# target: armv7-unknown-linux-gnueabihf
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -114,37 +97,124 @@ jobs:
|
||||
- name: Build for Linux
|
||||
uses: ./.github/build-for-linux
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
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') || startsWith(matrix.target, 'i686')
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
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."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
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."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
files: |
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
|
||||
|
||||
release-for-fixed-webview2:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
arch: x64
|
||||
- os: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
arch: x86
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: Download WebView2 Runtime
|
||||
run: |
|
||||
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
|
||||
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
|
||||
Remove-Item .\src-tauri\tauri.windows.conf.json
|
||||
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
|
||||
|
||||
- name: Tauri build
|
||||
id: build
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tauriScript: pnpm
|
||||
args: --target ${{ matrix.target }}
|
||||
|
||||
- name: Rename
|
||||
run: |
|
||||
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.exe' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.exe'
|
||||
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip'
|
||||
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip.sig' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip.sig'
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{steps.build.outputs.appVersion}}
|
||||
name: "Clash Verge Rev v${{steps.build.outputs.appVersion}}"
|
||||
body: "More new features are now supported."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
|
||||
|
||||
- name: Portable Bundle
|
||||
run: pnpm portable-fixed-webview2 ${{ matrix.target }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-update:
|
||||
needs: [release]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [release, release-for-linux]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -157,7 +227,7 @@ jobs:
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install
|
||||
@@ -167,3 +237,29 @@ jobs:
|
||||
run: pnpm updater
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-update-for-fixed-webview2:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [release-for-fixed-webview2]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install
|
||||
run: pnpm i
|
||||
|
||||
- name: Release updater file
|
||||
run: pnpm updater-fixed-webview2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
29
.github/workflows/updater.yml
vendored
@@ -10,14 +10,14 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install
|
||||
@@ -27,3 +27,28 @@ jobs:
|
||||
run: pnpm updater
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-update-for-fixed-webview2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install
|
||||
run: pnpm i
|
||||
|
||||
- name: Release updater file
|
||||
run: pnpm updater-fixed-webview2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
.pnpm-store
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
@@ -6,3 +7,4 @@ dist-ssr
|
||||
update.json
|
||||
scripts/_env.sh
|
||||
.vscode
|
||||
.tool-versions
|
||||
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
||||
nodejs 21.7.1
|
||||
59
README.md
@@ -1,5 +1,5 @@
|
||||
<h1 align="center">
|
||||
<img src="./src/assets/image/logo.png" alt="Clash" width="128" />
|
||||
<img src="./src-tauri/icons/icon.png" alt="Clash" width="128" />
|
||||
<br>
|
||||
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
|
||||
<br>
|
||||
@@ -9,36 +9,23 @@
|
||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||
</h3>
|
||||
|
||||
## Preview
|
||||
|
||||
| Dark | Light |
|
||||
| -------------------------------- | --------------------------------- |
|
||||
|  |  |
|
||||
|
||||
## 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).
|
||||
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
|
||||
Go to the [release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
|
||||
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.0/Clash.Verge_1.5.0_x64-setup.exe)]
|
||||
[[Windows x86](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/Clash.Verge_1.5.0_x86-setup.exe)]
|
||||
[[Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/Clash.Verge_1.5.0_arm64-setup.exe)]
|
||||
### 安装说明和常见问题,请到[文档页](https://clash-verge-rev.github.io/)查看:[Doc](https://clash-verge-rev.github.io/)
|
||||
|
||||
[[macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/Clash.Verge_1.5.0_x64.dmg)]
|
||||
[[macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/Clash.Verge_1.5.0_aarch64.dmg)]
|
||||
---
|
||||
|
||||
[[Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/clash-verge_1.5.0_amd64.AppImage)]
|
||||
[[Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/clash-verge_1.5.0_amd64.deb)]
|
||||
[[Linux x86 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/clash-verge_1.5.0_i386.AppImage)]
|
||||
[[Linux x86 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/clash-verge_1.5.0_i386.deb)]
|
||||
[[Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.0/clash-verge_1.5.0_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
|
||||
|
||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
||||
- Simple UI and supports custom theme color.
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
|
||||
#### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||
### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||
|
||||
## Promotion
|
||||
|
||||
@@ -54,15 +41,17 @@ Notes: If you could not start the app on Windows, please check that you have [We
|
||||
- 解锁流媒体及 ChatGPT
|
||||
- 官网:https://狗狗加速.com
|
||||
|
||||
## Preview
|
||||
## Features
|
||||
|
||||

|
||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
||||
- Improved UI and supports custom theme color.
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
|
||||
### FAQ
|
||||
|
||||
#### 1. **macOS** "Clash Verge" is damaged and can't be opened
|
||||
|
||||
open the terminal and run `sudo xattr -r -d com.apple.quarantine /Applications/Clash\ Verge.app`
|
||||
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/windows.html)
|
||||
|
||||
## Development
|
||||
|
||||
@@ -76,14 +65,6 @@ pnpm run check
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Todos
|
||||
|
||||
> This keng is a little big...
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This is a learning project for Rust practice.
|
||||
|
||||
## Contributions
|
||||
|
||||
Issue and PR welcome!
|
||||
|
||||
257
UPDATELOG.md
@@ -1,3 +1,260 @@
|
||||
## v1.6.5
|
||||
|
||||
### Features
|
||||
|
||||
- 添加 RPM 包支持
|
||||
- 优化细节
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- MacOS 10.15 编辑器空白的问题
|
||||
- MacOS 低版本启动白屏的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.6.4
|
||||
|
||||
### Features
|
||||
|
||||
- 系统代理支持 PAC 模式
|
||||
- 允许关闭不使用的端口
|
||||
- 使用新的应用图标
|
||||
- MacOS 支持切换托盘图标单色/彩色模式
|
||||
- CSS 注入支持通过编辑器编辑
|
||||
- 优化代理组列表性能
|
||||
- 优化流量图显性能
|
||||
- 支持波斯语
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Kill 内核后 Tun 开启缓慢的问题
|
||||
- 代理绕过为空时使用默认值
|
||||
- 无法读取剪切板内容
|
||||
- Windows 下覆盖安装无法内核占用问题
|
||||
|
||||
---
|
||||
|
||||
## v1.6.2
|
||||
|
||||
### Features
|
||||
|
||||
- 支持本地文件拖拽导入
|
||||
- 重新支持 32 位 CPU
|
||||
- 新增内置 Webview2 版本
|
||||
- 优化 Merge 逻辑,支持深度合并
|
||||
- 删除 Merge 配置中的 append/prepend-provider 字段
|
||||
- 支持更新稳定版内核
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- MacOS DNS 还原失败
|
||||
- CMD 环境变量格式错误
|
||||
- Linux 下与 N 卡的兼容性问题
|
||||
- 修改 Tun 设置不立即生效
|
||||
|
||||
---
|
||||
|
||||
## v1.6.1
|
||||
|
||||
### Features
|
||||
|
||||
- 鼠标悬浮显示当前订阅的名称 [#938](https://github.com/clash-verge-rev/clash-verge-rev/pull/938)
|
||||
- 日志过滤支持正则表达式 [#959](https://github.com/clash-verge-rev/clash-verge-rev/pull/959)
|
||||
- 更新 Clash 内核到 1.18.4
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 Linux KDE 环境下系统代理无法开启的问题
|
||||
- 窗口最大化图标调整 [#924](https://github.com/clash-verge-rev/clash-verge-rev/pull/924)
|
||||
- 修改 MacOS 托盘点击行为(左键菜单,右键点击事件)
|
||||
- 修复 MacOS 服务模式安装失败的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.6.0
|
||||
|
||||
### Features
|
||||
|
||||
- Meta(mihomo)内核回退 1.18.1(当前新版内核 hy2 协议有 bug,等修复后更新)
|
||||
- 多处界面细节调整 [#724](https://github.com/clash-verge-rev/clash-verge-rev/pull/724) [#799](https://github.com/clash-verge-rev/clash-verge-rev/pull/799) [#900](https://github.com/clash-verge-rev/clash-verge-rev/pull/900) [#901](https://github.com/clash-verge-rev/clash-verge-rev/pull/901)
|
||||
- Linux 下新增服务模式
|
||||
- 新增订阅卡片右键可以打开机场首页
|
||||
- url-test 支持手动选择、节点组 fixed 节点使用角标展示 [#840](https://github.com/clash-verge-rev/clash-verge-rev/pull/840)
|
||||
- Clash 配置、Merge 配置提供 JSON Schema 语法支持、连接界面调整 [#887](https://github.com/clash-verge-rev/clash-verge-rev/pull/887)
|
||||
- 修改 Merge 配置文件默认内容 [#889](https://github.com/clash-verge-rev/clash-verge-rev/pull/889)
|
||||
- 修改 tun 模式默认 mtu 为 1500,老版本升级,需在 tun 模式设置下“重置为默认值”。
|
||||
- 使用 npm 安装 meta-json-schema [#895](https://github.com/clash-verge-rev/clash-verge-rev/pull/895)
|
||||
- 更新部分翻译 [#904](https://github.com/clash-verge-rev/clash-verge-rev/pull/904)
|
||||
- 支持 ico 格式的任务栏图标
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 Linux KDE 环境下系统代理无法开启的问题
|
||||
- 修复延迟检测动画问题
|
||||
- 窗口最大化图标调整 [#816](https://github.com/clash-verge-rev/clash-verge-rev/pull/816)
|
||||
- 修复 Windows 某些情况下无法安装服务模式 [#822](https://github.com/clash-verge-rev/clash-verge-rev/pull/822)
|
||||
- UI 细节修复 [#821](https://github.com/clash-verge-rev/clash-verge-rev/pull/821)
|
||||
- 修复使用默认编辑器打开配置文件
|
||||
- 修复内核文件在特定目录也可以更新的问题 [#857](https://github.com/clash-verge-rev/clash-verge-rev/pull/857)
|
||||
- 修复服务模式的安装目录问题
|
||||
- 修复删除配置文件的“更新间隔”出现的问题 [#907](https://github.com/clash-verge-rev/clash-verge-rev/issues/907)
|
||||
|
||||
### 已知问题(历史遗留问题,暂未找到有效解决方案)
|
||||
|
||||
- MacOS M 芯片下服务模式无法安装;临时解决方案:在内核 ⚙️ 下,手动授权,再打开 tun 模式。
|
||||
- MacOS 下如果删除过网络配置,会导致无法正常打开系统代理;临时解决方案:使用浏览器代理插件或手动配置系统代理。
|
||||
- Window 拨号连接下无法正确识别并打开系统代理;临时解决方案:使用浏览器代理插件或使用 tun 模式。
|
||||
|
||||
---
|
||||
|
||||
## v1.5.11
|
||||
|
||||
### Features
|
||||
|
||||
- Meta(mihomo)内核更新 1.18.2
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 升级图标无法点击的问题
|
||||
- 卸载时检查安装目录是否为空
|
||||
- 代理界面图标重合的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.10
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 Linux 托盘菜单显示
|
||||
- 添加透明代理端口设置
|
||||
- 删除订阅前确认
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 删除 MacOS 程序坞图标
|
||||
- Windows 下 service 日志没有清理
|
||||
- MacOS 无法开启系统代理
|
||||
|
||||
---
|
||||
|
||||
## v1.5.9
|
||||
|
||||
### Features
|
||||
|
||||
- 缓存代理组图标
|
||||
- 使用`boa_engine` 代替 `rquickjs`
|
||||
- 支持 Linux armv7
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Windows 首次安装无法点击
|
||||
- Windows 触摸屏无法拖动
|
||||
- 规则列表 `REJECT-DROP` 颜色
|
||||
- MacOS Dock 栏不显示图标
|
||||
- MacOS 自定义字体无效
|
||||
- 避免使用空 UA 拉取订阅
|
||||
|
||||
---
|
||||
|
||||
## v1.5.8
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 UI 细节
|
||||
- Linux 绘制窗口圆角
|
||||
- 开放 DevTools
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 MacOS 下开启 Tun 内核崩溃的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.7
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 UI 各种细节
|
||||
- 提供菜单栏图标样式切换选项(单色/彩色/禁用)
|
||||
- 添加自动检查更新开关
|
||||
- MacOS 开启 Tun 模式自动修改 DNS
|
||||
- 调整可拖动区域(尝试修复触摸屏无法拖动的问题)
|
||||
|
||||
---
|
||||
|
||||
## v1.5.6
|
||||
|
||||
### Features
|
||||
|
||||
- 全新专属 Verge rev UI 界面 (by @Amnesiash) 及细节调整
|
||||
- 提供允许无效证书的开关
|
||||
- 删除不必要的快捷键
|
||||
- Provider 更新添加动画
|
||||
- Merge 支持 Provider
|
||||
- 更换订阅框的粘贴按钮,删除默认的"Remote File" Profile 名称
|
||||
- 链接菜单添加节点显示
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Linux 下图片显示错误
|
||||
|
||||
---
|
||||
|
||||
## v1.5.4
|
||||
|
||||
### Features
|
||||
|
||||
- 支持自定义托盘图标
|
||||
- 支持禁用代理组图标
|
||||
- 代理组显示当前代理
|
||||
- 修改 `打开面板` 快捷键为`打开/关闭面板`
|
||||
|
||||
---
|
||||
|
||||
## v1.5.3
|
||||
|
||||
### Features
|
||||
|
||||
- Tun 设置添加重置按钮
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Tun 设置项显示错误的问题
|
||||
- 修改一些默认值
|
||||
- 启动时不更改启动项设置
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
BIN
docs/preview.gif
|
Before Width: | Height: | Size: 6.7 MiB |
BIN
docs/preview_dark.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
docs/preview_light.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
62
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.5",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
@@ -12,52 +12,59 @@
|
||||
"web:serve": "vite preview",
|
||||
"check": "node scripts/check.mjs",
|
||||
"updater": "node scripts/updater.mjs",
|
||||
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
|
||||
"portable": "node scripts/portable.mjs",
|
||||
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@mui/icons-material": "^5.15.5",
|
||||
"@mui/icons-material": "^5.15.19",
|
||||
"@mui/lab": "5.0.0-alpha.149",
|
||||
"@mui/material": "^5.15.5",
|
||||
"@mui/x-data-grid": "^6.18.7",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"ahooks": "^3.7.8",
|
||||
"axios": "^1.6.5",
|
||||
"@mui/material": "^5.15.19",
|
||||
"@mui/x-data-grid": "^6.20.0",
|
||||
"@tauri-apps/api": "^1.5.6",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"ahooks": "^3.8.0",
|
||||
"axios": "^1.7.2",
|
||||
"dayjs": "1.11.5",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next": "^23.11.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"meta-json-schema": "1.18.5-alpha4",
|
||||
"monaco-editor": "^0.49.0",
|
||||
"monaco-yaml": "^5.1.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-hook-form": "^7.51.5",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-router-dom": "^6.21.2",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-virtuoso": "^4.6.2",
|
||||
"react-virtuoso": "^4.7.11",
|
||||
"recoil": "^0.7.7",
|
||||
"snarkdown": "^2.0.0",
|
||||
"swr": "^1.3.0",
|
||||
"tar": "^6.2.0"
|
||||
"tar": "^6.2.1",
|
||||
"types-pac": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^5.1.1",
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@tauri-apps/cli": "^1.5.14",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"adm-zip": "^0.5.10",
|
||||
"@vitejs/plugin-legacy": "^5.4.1",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"adm-zip": "^0.5.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
@@ -65,9 +72,10 @@
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"sass": "^1.70.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.11",
|
||||
"sass": "^1.77.4",
|
||||
"terser": "^5.31.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.12",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
},
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
From 8b085aea2f11e64f433244eda092c178a2bb50bc Mon Sep 17 00:00:00 2001
|
||||
From: MystiPanda <mystipanda@proton.me>
|
||||
Date: Sun, 10 Dec 2023 19:47:45 +0800
|
||||
Subject: [PATCH] feat: Support windows aarch64
|
||||
|
||||
---
|
||||
.gitmodules | 3 +
|
||||
src-tauri/Cargo.toml | 2 +-
|
||||
src-tauri/quick-rs | 1 +
|
||||
src-tauri/src/enhance/script.rs | 130 +++++++++++++++++++-------------
|
||||
4 files changed, 81 insertions(+), 55 deletions(-)
|
||||
create mode 100644 .gitmodules
|
||||
create mode 160000 src-tauri/quick-rs
|
||||
|
||||
diff --git a/.gitmodules b/.gitmodules
|
||||
new file mode 100644
|
||||
index 0000000..2eda7e4
|
||||
--- /dev/null
|
||||
+++ b/.gitmodules
|
||||
@@ -0,0 +1,3 @@
|
||||
+[submodule "src-tauri/quick-rs"]
|
||||
+ path = src-tauri/quick-rs
|
||||
+ url = https://github.com/clash-verge-rev/quick-rs.git
|
||||
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
|
||||
index 4c6dde5..5fd9ad8 100644
|
||||
--- a/src-tauri/Cargo.toml
|
||||
+++ b/src-tauri/Cargo.toml
|
||||
@@ -25,7 +25,6 @@ log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.30"
|
||||
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
auto-launch = "0.5"
|
||||
@@ -34,6 +33,7 @@ port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.5"
|
||||
parking_lot = "0.12"
|
||||
percent-encoding = "2.3.1"
|
||||
+quick-rs = { path = "quick-rs" }
|
||||
window-shadows = { version = "0.2" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
diff --git a/src-tauri/quick-rs b/src-tauri/quick-rs
|
||||
new file mode 160000
|
||||
index 0000000..78277c4
|
||||
--- /dev/null
|
||||
+++ b/src-tauri/quick-rs
|
||||
@@ -0,0 +1 @@
|
||||
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
||||
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
||||
index 6c207d9..d47dc33 100644
|
||||
--- a/src-tauri/src/enhance/script.rs
|
||||
+++ b/src-tauri/src/enhance/script.rs
|
||||
@@ -3,61 +3,83 @@ use anyhow::Result;
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||
- use rquickjs::{function::Func, Context, Runtime};
|
||||
- use std::sync::{Arc, Mutex};
|
||||
-
|
||||
- let runtime = Runtime::new().unwrap();
|
||||
- let context = Context::full(&runtime).unwrap();
|
||||
- let outputs = Arc::new(Mutex::new(vec![]));
|
||||
-
|
||||
- let copy_outputs = outputs.clone();
|
||||
- let result = context.with(|ctx| -> Result<Mapping> {
|
||||
- ctx.globals().set(
|
||||
- "__verge_log__",
|
||||
- Func::from(move |level: String, data: String| {
|
||||
- let mut out = copy_outputs.lock().unwrap();
|
||||
- out.push((level, data));
|
||||
- }),
|
||||
- )?;
|
||||
-
|
||||
- ctx.eval(
|
||||
- r#"var console = Object.freeze({
|
||||
- log(data){__verge_log__("log",JSON.stringify(data))},
|
||||
- info(data){__verge_log__("info",JSON.stringify(data))},
|
||||
- error(data){__verge_log__("error",JSON.stringify(data))},
|
||||
- debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||
- });"#,
|
||||
- )?;
|
||||
-
|
||||
- let config = use_lowercase(config.clone());
|
||||
- let config_str = serde_json::to_string(&config)?;
|
||||
-
|
||||
- let code = format!(
|
||||
- r#"try{{
|
||||
+ use quick_rs::{context::Context, function::Function, module::Module, runtime::Runtime};
|
||||
+
|
||||
+ let config = use_lowercase(config.clone());
|
||||
+ let config_str = serde_json::to_string(&config)?;
|
||||
+
|
||||
+ let runtime = Runtime::new();
|
||||
+ let context = Context::from(&runtime);
|
||||
+
|
||||
+ let code = format!(
|
||||
+ r#"
|
||||
+ let output = [];
|
||||
+
|
||||
+ function __verge_log__(type, data) {{
|
||||
+ output.push([type, data]);
|
||||
+ }}
|
||||
+
|
||||
+ var console = Object.freeze({{
|
||||
+ log(data) {{ __verge_log__("log", JSON.stringify(data)) }},
|
||||
+ info(data) {{ __verge_log__("info", JSON.stringify(data)) }},
|
||||
+ error(data) {{ __verge_log__("error", JSON.stringify(data)) }},
|
||||
+ debug(data) {{ __verge_log__("debug", JSON.stringify(data)) }},
|
||||
+ }});
|
||||
+
|
||||
{script};
|
||||
- JSON.stringify(main({config_str})||'')
|
||||
- }} catch(err) {{
|
||||
- `__error_flag__ ${{err.toString()}}`
|
||||
- }}"#
|
||||
- );
|
||||
- let result: String = ctx.eval(code.as_str())?;
|
||||
- if result.starts_with("__error_flag__") {
|
||||
- anyhow::bail!(result[15..].to_owned());
|
||||
- }
|
||||
- if result == "\"\"" {
|
||||
- anyhow::bail!("main function should return object");
|
||||
- }
|
||||
- Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
- });
|
||||
-
|
||||
- let mut out = outputs.lock().unwrap();
|
||||
- match result {
|
||||
- Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
- Err(err) => {
|
||||
- out.push(("exception".into(), err.to_string()));
|
||||
- Ok((config, out.to_vec()))
|
||||
- }
|
||||
- }
|
||||
+
|
||||
+ export function _main(){{
|
||||
+ try{{
|
||||
+ let result = JSON.stringify(main({config_str})||"");
|
||||
+ return JSON.stringify({{result, output}});
|
||||
+ }} catch(err) {{
|
||||
+ output.push(["exception", err.toString()]);
|
||||
+ return JSON.stringify({{result: "__error__", output}});
|
||||
+ }}
|
||||
+ }}
|
||||
+ "#
|
||||
+ );
|
||||
+ let value = context.eval_module(&code, "_main")?;
|
||||
+ let module = Module::new(value)?;
|
||||
+ let value = module.get("_main")?;
|
||||
+ let function = Function::new(value)?;
|
||||
+ let value = function.call(vec![])?;
|
||||
+ let result = serde_json::from_str::<serde_json::Value>(&value.to_string()?)?;
|
||||
+ result
|
||||
+ .as_object()
|
||||
+ .map(|obj| {
|
||||
+ let result = obj.get("result").unwrap().as_str().unwrap();
|
||||
+ let output = obj.get("output").unwrap();
|
||||
+
|
||||
+ let mut out = output
|
||||
+ .as_array()
|
||||
+ .unwrap()
|
||||
+ .iter()
|
||||
+ .map(|item| {
|
||||
+ let item = item.as_array().unwrap();
|
||||
+ (
|
||||
+ item[0].as_str().unwrap().into(),
|
||||
+ item[1].as_str().unwrap().into(),
|
||||
+ )
|
||||
+ })
|
||||
+ .collect::<Vec<_>>();
|
||||
+ if result.is_empty() {
|
||||
+ anyhow::bail!("main function should return object");
|
||||
+ }
|
||||
+ if result == "__error__" {
|
||||
+ return Ok((config, out.to_vec()));
|
||||
+ }
|
||||
+ let result = serde_json::from_str::<Mapping>(result);
|
||||
+
|
||||
+ match result {
|
||||
+ Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
+ Err(err) => {
|
||||
+ out.push(("exception".into(), err.to_string()));
|
||||
+ Ok((config, out.to_vec()))
|
||||
+ }
|
||||
+ }
|
||||
+ })
|
||||
+ .unwrap_or_else(|| anyhow::bail!("Unknown result"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
6969
pnpm-lock.yaml
generated
@@ -21,6 +21,7 @@ const PLATFORM_MAP = {
|
||||
"i686-unknown-linux-gnu": "linux",
|
||||
"aarch64-unknown-linux-gnu": "linux",
|
||||
"armv7-unknown-linux-gnueabihf": "linux",
|
||||
"riscv64gc-unknown-linux-gnu": "linux",
|
||||
"loongarch64-unknown-linux-gnu": "linux",
|
||||
};
|
||||
const ARCH_MAP = {
|
||||
@@ -33,6 +34,7 @@ const ARCH_MAP = {
|
||||
"i686-unknown-linux-gnu": "ia32",
|
||||
"aarch64-unknown-linux-gnu": "arm64",
|
||||
"armv7-unknown-linux-gnueabihf": "arm",
|
||||
"riscv64gc-unknown-linux-gnu": "riscv64",
|
||||
"loongarch64-unknown-linux-gnu": "loong64",
|
||||
};
|
||||
|
||||
@@ -59,12 +61,13 @@ const META_ALPHA_MAP = {
|
||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||
"win32-ia32": "mihomo-windows-386",
|
||||
"win32-arm64": "mihomo-windows-arm64",
|
||||
"darwin-x64": "mihomo-darwin-amd64",
|
||||
"darwin-x64": "mihomo-darwin-amd64-compatible",
|
||||
"darwin-arm64": "mihomo-darwin-arm64",
|
||||
"linux-x64": "mihomo-linux-amd64-compatible",
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-riscv64": "mihomo-linux-riscv64",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
@@ -105,12 +108,13 @@ const META_MAP = {
|
||||
"win32-x64": "mihomo-windows-amd64-compatible",
|
||||
"win32-ia32": "mihomo-windows-386",
|
||||
"win32-arm64": "mihomo-windows-arm64",
|
||||
"darwin-x64": "mihomo-darwin-amd64",
|
||||
"darwin-x64": "mihomo-darwin-amd64-compatible",
|
||||
"darwin-arm64": "mihomo-darwin-arm64",
|
||||
"linux-x64": "mihomo-linux-amd64-compatible",
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-riscv64": "mihomo-linux-riscv64",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
@@ -317,26 +321,94 @@ async function downloadFile(url, path) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// service chmod
|
||||
const resolveServicePermission = async () => {
|
||||
const serviceExecutables = [
|
||||
"clash-verge-service",
|
||||
"install-service",
|
||||
"uninstall-service",
|
||||
];
|
||||
const resDir = path.join(cwd, "src-tauri/resources");
|
||||
for (let f of serviceExecutables) {
|
||||
const targetPath = path.join(resDir, f);
|
||||
if (await fs.pathExists(targetPath)) {
|
||||
execSync(`chmod 755 ${targetPath}`);
|
||||
console.log(`[INFO]: "${targetPath}" chmod finished`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* main
|
||||
*/
|
||||
|
||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
||||
|
||||
const resolveService = () =>
|
||||
const resolveService = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
resolveResource({
|
||||
file: "clash-verge-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service.exe`,
|
||||
file: "clash-verge-service" + ext,
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
|
||||
});
|
||||
const resolveInstall = () =>
|
||||
};
|
||||
|
||||
const resolveInstall = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
resolveResource({
|
||||
file: "install-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/install-service.exe`,
|
||||
file: "install-service" + ext,
|
||||
downloadURL: `${SERVICE_URL}/install-service${ext}`,
|
||||
});
|
||||
const resolveUninstall = () =>
|
||||
};
|
||||
|
||||
const resolveUninstall = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
resolveResource({
|
||||
file: "uninstall-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
|
||||
file: "uninstall-service" + ext,
|
||||
downloadURL: `${SERVICE_URL}/uninstall-service${ext}`,
|
||||
});
|
||||
};
|
||||
|
||||
const resolveSetDnsScript = () =>
|
||||
resolveResource({
|
||||
file: "set_dns.sh",
|
||||
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/set_dns.sh`,
|
||||
});
|
||||
const resolveUnSetDnsScript = () =>
|
||||
resolveResource({
|
||||
file: "unset_dns.sh",
|
||||
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/unset_dns.sh`,
|
||||
});
|
||||
const resolveMmdb = () =>
|
||||
resolveResource({
|
||||
@@ -373,9 +445,12 @@ const tasks = [
|
||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||
retry: 5,
|
||||
},
|
||||
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
||||
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
|
||||
{ name: "service", func: resolveService, retry: 5 },
|
||||
{ name: "install", func: resolveInstall, retry: 5 },
|
||||
{ name: "uninstall", func: resolveUninstall, retry: 5 },
|
||||
{ name: "set_dns_script", func: resolveSetDnsScript, retry: 5 },
|
||||
{ name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 },
|
||||
{ name: "mmdb", func: resolveMmdb, retry: 5 },
|
||||
{ name: "geosite", func: resolveGeosite, retry: 5 },
|
||||
{ name: "geoip", func: resolveGeoIP, retry: 5 },
|
||||
@@ -385,12 +460,20 @@ const tasks = [
|
||||
retry: 5,
|
||||
winOnly: true,
|
||||
},
|
||||
{
|
||||
name: "service_chmod",
|
||||
func: resolveServicePermission,
|
||||
retry: 1,
|
||||
unixOnly: true,
|
||||
},
|
||||
];
|
||||
|
||||
async function runTask() {
|
||||
const task = tasks.shift();
|
||||
if (!task) return;
|
||||
if (task.winOnly && process.platform !== "win32") return runTask();
|
||||
if (task.winOnly && platform !== "win32") return runTask();
|
||||
if (task.linuxOnly && platform !== "linux") return runTask();
|
||||
if (task.unixOnly && platform === "win32") return runTask();
|
||||
|
||||
for (let i = 0; i < task.retry; i++) {
|
||||
try {
|
||||
|
||||
100
scripts/portable-fixed-webview2.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import AdmZip from "adm-zip";
|
||||
import { createRequire } from "module";
|
||||
import { getOctokit, context } from "@actions/github";
|
||||
|
||||
const target = process.argv.slice(2)[0];
|
||||
const alpha = process.argv.slice(2)[1];
|
||||
|
||||
const ARCH_MAP = {
|
||||
"x86_64-pc-windows-msvc": "x64",
|
||||
"i686-pc-windows-msvc": "x86",
|
||||
"aarch64-pc-windows-msvc": "arm64",
|
||||
};
|
||||
|
||||
const PROCESS_MAP = {
|
||||
x64: "x64",
|
||||
ia32: "x86",
|
||||
arm64: "arm64",
|
||||
};
|
||||
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
|
||||
/// Script for ci
|
||||
/// 打包绿色版/便携版 (only Windows)
|
||||
async function resolvePortable() {
|
||||
if (process.platform !== "win32") return;
|
||||
|
||||
const releaseDir = target
|
||||
? `./src-tauri/target/${target}/release`
|
||||
: `./src-tauri/target/release`;
|
||||
|
||||
const configDir = path.join(releaseDir, ".config");
|
||||
|
||||
if (!(await fs.pathExists(releaseDir))) {
|
||||
throw new Error("could not found the release dir");
|
||||
}
|
||||
|
||||
await fs.mkdir(configDir);
|
||||
await fs.createFile(path.join(configDir, "PORTABLE"));
|
||||
|
||||
const zip = new AdmZip();
|
||||
|
||||
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
|
||||
zip.addLocalFile(path.join(releaseDir, "clash-meta.exe"));
|
||||
zip.addLocalFile(path.join(releaseDir, "clash-meta-alpha.exe"));
|
||||
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||
zip.addLocalFolder(
|
||||
path.join(
|
||||
releaseDir,
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
|
||||
),
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
|
||||
);
|
||||
zip.addLocalFolder(configDir, ".config");
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const packageJson = require("../package.json");
|
||||
const { version } = packageJson;
|
||||
|
||||
const zipFile = `Clash.Verge_${version}_${arch}_fixed_webview2_portable.zip`;
|
||||
zip.writeZip(zipFile);
|
||||
|
||||
console.log("[INFO]: create portable zip successfully");
|
||||
|
||||
// push release assets
|
||||
if (process.env.GITHUB_TOKEN === undefined) {
|
||||
throw new Error("GITHUB_TOKEN is required");
|
||||
}
|
||||
|
||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
|
||||
console.log("[INFO]: upload to ", tag);
|
||||
|
||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||
...options,
|
||||
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);
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: release.id,
|
||||
name: zipFile,
|
||||
data: zip.toBuffer(),
|
||||
});
|
||||
}
|
||||
|
||||
resolvePortable().catch(console.error);
|
||||
@@ -5,13 +5,20 @@ import { createRequire } from "module";
|
||||
import { getOctokit, context } from "@actions/github";
|
||||
|
||||
const target = process.argv.slice(2)[0];
|
||||
const alpha = process.argv.slice(2)[1];
|
||||
|
||||
const ARCH_MAP = {
|
||||
"i686-pc-windows-msvc": "x86",
|
||||
"x86_64-pc-windows-msvc": "x64",
|
||||
"i686-pc-windows-msvc": "x86",
|
||||
"aarch64-pc-windows-msvc": "arm64",
|
||||
};
|
||||
|
||||
const PROCESS_MAP = {
|
||||
x64: "x64",
|
||||
ia32: "x86",
|
||||
arm64: "arm64",
|
||||
};
|
||||
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
|
||||
/// Script for ci
|
||||
/// 打包绿色版/便携版 (only Windows)
|
||||
async function resolvePortable() {
|
||||
@@ -41,7 +48,7 @@ async function resolvePortable() {
|
||||
const packageJson = require("../package.json");
|
||||
const { version } = packageJson;
|
||||
|
||||
const zipFile = `Clash.Verge_${version}_${ARCH_MAP[target]}_portable.zip`;
|
||||
const zipFile = `Clash.Verge_${version}_${arch}_portable.zip`;
|
||||
zip.writeZip(zipFile);
|
||||
|
||||
console.log("[INFO]: create portable zip successfully");
|
||||
@@ -53,14 +60,25 @@ async function resolvePortable() {
|
||||
|
||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||
|
||||
console.log("[INFO]: upload to ", process.env.TAG_NAME || `v${version}`);
|
||||
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
|
||||
console.log("[INFO]: upload to ", tag);
|
||||
|
||||
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||
...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);
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
|
||||
154
scripts/updater-fixed-webview2.mjs
Normal file
@@ -0,0 +1,154 @@
|
||||
import fetch from "node-fetch";
|
||||
import { getOctokit, context } from "@actions/github";
|
||||
import { resolveUpdateLog } from "./updatelog.mjs";
|
||||
|
||||
const UPDATE_TAG_NAME = "updater";
|
||||
const UPDATE_JSON_FILE = "update-fixed-webview2.json";
|
||||
const UPDATE_JSON_PROXY = "update-fixed-webview2-proxy.json";
|
||||
|
||||
/// generate update.json
|
||||
/// upload to update tag's release asset
|
||||
async function resolveUpdater() {
|
||||
if (process.env.GITHUB_TOKEN === undefined) {
|
||||
throw new Error("GITHUB_TOKEN is required");
|
||||
}
|
||||
|
||||
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||
|
||||
const { data: tags } = await github.rest.repos.listTags({
|
||||
...options,
|
||||
per_page: 10,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
// get the latest publish tag
|
||||
const tag = tags.find((t) => t.name.startsWith("v"));
|
||||
|
||||
console.log(tag);
|
||||
console.log();
|
||||
|
||||
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
|
||||
...options,
|
||||
tag: tag.name,
|
||||
});
|
||||
|
||||
const updateData = {
|
||||
name: tag.name,
|
||||
notes: await resolveUpdateLog(tag.name), // use updatelog.md
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms: {
|
||||
"windows-x86_64": { signature: "", url: "" },
|
||||
"windows-aarch64": { signature: "", url: "" },
|
||||
"windows-x86": { signature: "", url: "" },
|
||||
},
|
||||
};
|
||||
|
||||
const promises = latestRelease.assets.map(async (asset) => {
|
||||
const { name, browser_download_url } = asset;
|
||||
|
||||
// win64 url
|
||||
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
||||
}
|
||||
// win64 signature
|
||||
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip.sig")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-x86_64"].signature = sig;
|
||||
}
|
||||
|
||||
// win32 url
|
||||
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-x86"].url = browser_download_url;
|
||||
}
|
||||
// win32 signature
|
||||
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip.sig")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-x86"].signature = sig;
|
||||
}
|
||||
|
||||
// win arm url
|
||||
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-aarch64"].url = browser_download_url;
|
||||
}
|
||||
// win arm signature
|
||||
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip.sig")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-aarch64"].signature = sig;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
console.log(updateData);
|
||||
|
||||
// maybe should test the signature as well
|
||||
// delete the null field
|
||||
Object.entries(updateData.platforms).forEach(([key, value]) => {
|
||||
if (!value.url) {
|
||||
console.log(`[Error]: failed to parse release for "${key}"`);
|
||||
delete updateData.platforms[key];
|
||||
}
|
||||
});
|
||||
|
||||
// 生成一个代理github的更新文件
|
||||
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
|
||||
const updateDataNew = JSON.parse(JSON.stringify(updateData));
|
||||
|
||||
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
|
||||
if (value.url) {
|
||||
updateDataNew.platforms[key].url =
|
||||
"https://mirror.ghproxy.com/" + value.url;
|
||||
} else {
|
||||
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
|
||||
}
|
||||
});
|
||||
|
||||
// update the update.json
|
||||
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
|
||||
...options,
|
||||
tag: UPDATE_TAG_NAME,
|
||||
});
|
||||
|
||||
// delete the old assets
|
||||
for (let asset of updateRelease.assets) {
|
||||
if (asset.name === UPDATE_JSON_FILE) {
|
||||
await github.rest.repos.deleteReleaseAsset({
|
||||
...options,
|
||||
asset_id: asset.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (asset.name === UPDATE_JSON_PROXY) {
|
||||
await github.rest.repos
|
||||
.deleteReleaseAsset({ ...options, asset_id: asset.id })
|
||||
.catch(console.error); // do not break the pipeline
|
||||
}
|
||||
}
|
||||
|
||||
// upload new assets
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: updateRelease.id,
|
||||
name: UPDATE_JSON_FILE,
|
||||
data: JSON.stringify(updateData, null, 2),
|
||||
});
|
||||
|
||||
await github.rest.repos.uploadReleaseAsset({
|
||||
...options,
|
||||
release_id: updateRelease.id,
|
||||
name: UPDATE_JSON_PROXY,
|
||||
data: JSON.stringify(updateDataNew, null, 2),
|
||||
});
|
||||
}
|
||||
|
||||
// get the signature file content
|
||||
async function getSignature(url) {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/octet-stream" },
|
||||
});
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
resolveUpdater().catch(console.error);
|
||||
@@ -45,12 +45,12 @@ async function resolveUpdater() {
|
||||
"darwin-intel": { signature: "", url: "" },
|
||||
"darwin-x86_64": { signature: "", url: "" },
|
||||
"linux-x86_64": { signature: "", url: "" },
|
||||
"linux-i686": { signature: "", url: "" },
|
||||
"linux-x86": { signature: "", url: "" },
|
||||
"linux-aarch64": { signature: "", url: "" },
|
||||
"linux-armv7": { signature: "", url: "" },
|
||||
"windows-x86_64": { signature: "", url: "" },
|
||||
"windows-i686": { signature: "", url: "" },
|
||||
"windows-aarch64": { signature: "", url: "" },
|
||||
"windows-x86": { signature: "", url: "" },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -70,13 +70,13 @@ async function resolveUpdater() {
|
||||
}
|
||||
|
||||
// win32 url
|
||||
if (name.endsWith("x86-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-i686"].url = browser_download_url;
|
||||
if (name.endsWith("x64-setup.nsis.zip")) {
|
||||
updateData.platforms["windows-x86"].url = browser_download_url;
|
||||
}
|
||||
// win32 signature
|
||||
if (name.endsWith("x86-setup.nsis.zip.sig")) {
|
||||
if (name.endsWith("x64-setup.nsis.zip.sig")) {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms["windows-i686"].signature = sig;
|
||||
updateData.platforms["windows-x86"].signature = sig;
|
||||
}
|
||||
|
||||
// win arm url
|
||||
@@ -117,6 +117,7 @@ async function resolveUpdater() {
|
||||
if (name.endsWith("amd64.AppImage.tar.gz")) {
|
||||
updateData.platforms.linux.url = browser_download_url;
|
||||
updateData.platforms["linux-x86_64"].url = browser_download_url;
|
||||
updateData.platforms["linux-x86"].url = browser_download_url;
|
||||
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
|
||||
updateData.platforms["linux-aarch64"].url = browser_download_url;
|
||||
updateData.platforms["linux-armv7"].url = browser_download_url;
|
||||
@@ -126,20 +127,11 @@ async function resolveUpdater() {
|
||||
const sig = await getSignature(browser_download_url);
|
||||
updateData.platforms.linux.signature = sig;
|
||||
updateData.platforms["linux-x86_64"].signature = sig;
|
||||
updateData.platforms["linux-x86"].url = browser_download_url;
|
||||
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
|
||||
updateData.platforms["linux-aarch64"].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);
|
||||
|
||||
2769
src-tauri/Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "1.5.0"
|
||||
version = "1.6.5"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -10,43 +10,41 @@ edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1", features = [] }
|
||||
tauri-build = { git="https://github.com/Pylogmon/tauri",branch = "1.x", features = [] }
|
||||
|
||||
[dependencies]
|
||||
warp = "0.3"
|
||||
which = "6.0.0"
|
||||
anyhow = "1.0"
|
||||
dirs = "5.0"
|
||||
open = "5.0"
|
||||
open = "5.1"
|
||||
log = "0.4"
|
||||
ctrlc = "3.4"
|
||||
dunce = "1.0"
|
||||
log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.30"
|
||||
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
boa_engine = "0.18"
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
auto-launch = "0.5"
|
||||
once_cell = "1.18"
|
||||
once_cell = "1.19"
|
||||
port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.5"
|
||||
delay_timer = "0.11"
|
||||
parking_lot = "0.12"
|
||||
percent-encoding = "2.3.1"
|
||||
window-shadows = { version = "0.2" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", 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"] }
|
||||
|
||||
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
|
||||
tauri = { git="https://github.com/Pylogmon/tauri",branch = "1.x", features = [ "fs-read-file", "fs-exists", "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "icon-ico", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] }
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.0.0" # 高版本会返回错误 Status
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
winreg = "0.52.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
users = "0.11.0"
|
||||
#openssl
|
||||
|
||||
[features]
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/tray-icon-mono.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src-tauri/icons/tray-icon-sys-mono.ico
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src-tauri/icons/tray-icon-sys.ico
Normal file
|
After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/tray-icon-tun-mono.ico
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src-tauri/icons/tray-icon-tun.ico
Normal file
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -8,8 +8,8 @@ use crate::{ret_err, wrap_err};
|
||||
use anyhow::{Context, Result};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::api;
|
||||
use sysproxy::{Autoproxy, Sysproxy};
|
||||
use tauri::{api, Manager};
|
||||
type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
#[tauri::command]
|
||||
@@ -65,6 +65,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
let _ = handle::Handle::update_systray_part();
|
||||
Config::profiles().apply();
|
||||
wrap_err!(Config::profiles().data().save_file())?;
|
||||
Ok(())
|
||||
@@ -193,7 +194,6 @@ pub fn grant_permission(_core: String) -> CmdResult {
|
||||
#[tauri::command]
|
||||
pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||
let current = wrap_err!(Sysproxy::get_system_proxy())?;
|
||||
|
||||
let mut map = Mapping::new();
|
||||
map.insert("enable".into(), current.enable.into());
|
||||
map.insert(
|
||||
@@ -205,6 +205,18 @@ pub fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// get the system proxy
|
||||
#[tauri::command]
|
||||
pub fn get_auto_proxy() -> CmdResult<Mapping> {
|
||||
let current = wrap_err!(Autoproxy::get_auto_proxy())?;
|
||||
|
||||
let mut map = Mapping::new();
|
||||
map.insert("enable".into(), current.enable.into());
|
||||
map.insert("url".into(), current.url.into());
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
|
||||
Ok(logger::Logger::global().get_log())
|
||||
@@ -249,8 +261,9 @@ pub mod uwp {
|
||||
pub async fn clash_api_get_proxy_delay(
|
||||
name: String,
|
||||
url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> 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),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
@@ -266,6 +279,69 @@ pub async fn test_delay(url: String) -> CmdResult<u32> {
|
||||
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_app_dir() -> CmdResult<String> {
|
||||
let app_home_dir = wrap_err!(dirs::app_home_dir())?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
Ok(app_home_dir)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
|
||||
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
|
||||
let icon_path = icon_cache_dir.join(name);
|
||||
if !icon_cache_dir.exists() {
|
||||
let _ = std::fs::create_dir_all(&icon_cache_dir);
|
||||
}
|
||||
if !icon_path.exists() {
|
||||
let response = wrap_err!(reqwest::get(url).await)?;
|
||||
|
||||
let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
|
||||
|
||||
let content = wrap_err!(response.bytes().await)?;
|
||||
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
|
||||
}
|
||||
Ok(icon_path.to_string_lossy().to_string())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
let file_path = std::path::Path::new(&path);
|
||||
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
|
||||
if !icon_dir.exists() {
|
||||
let _ = std::fs::create_dir_all(&icon_dir);
|
||||
}
|
||||
let ext = match file_path.extension() {
|
||||
Some(e) => e.to_string_lossy().to_string(),
|
||||
None => "ico".to_string(),
|
||||
};
|
||||
|
||||
let png_dest_path = icon_dir.join(format!("{name}.png"));
|
||||
let ico_dest_path = icon_dir.join(format!("{name}.ico"));
|
||||
let dest_path = icon_dir.join(format!("{name}.{ext}"));
|
||||
if file_path.exists() {
|
||||
std::fs::remove_file(png_dest_path).unwrap_or_default();
|
||||
std::fs::remove_file(ico_dest_path).unwrap_or_default();
|
||||
match std::fs::copy(file_path, &dest_path) {
|
||||
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
} else {
|
||||
return Err("file not found".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_devtools(app_handle: tauri::AppHandle) {
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
if !window.is_devtools_open() {
|
||||
window.open_devtools();
|
||||
} else {
|
||||
window.close_devtools();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
@@ -275,42 +351,23 @@ pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use crate::core::win_service;
|
||||
use crate::core::service;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_service() -> CmdResult<win_service::JsonResponse> {
|
||||
wrap_err!(win_service::check_service().await)
|
||||
pub async fn check_service() -> CmdResult<service::JsonResponse> {
|
||||
wrap_err!(service::check_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult {
|
||||
wrap_err!(win_service::install_service().await)
|
||||
wrap_err!(service::install_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
wrap_err!(win_service::uninstall_service().await)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub mod service {
|
||||
use super::*;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
Ok(())
|
||||
wrap_err!(service::uninstall_service().await)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,18 +12,37 @@ pub struct IClashTemp(pub Mapping);
|
||||
|
||||
impl IClashTemp {
|
||||
pub fn new() -> Self {
|
||||
let template = Self::template();
|
||||
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) => {
|
||||
log::error!(target: "app", "{err}");
|
||||
Self::template()
|
||||
template
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn template() -> Self {
|
||||
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(), false.into());
|
||||
tun.insert("auto-detect-interface".into(), true.into());
|
||||
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
||||
tun.insert("mtu".into(), 1500.into());
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
map.insert("redir-port".into(), 7895.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
map.insert("tproxy-port".into(), 7896.into());
|
||||
map.insert("mixed-port".into(), 7897.into());
|
||||
map.insert("socks-port".into(), 7898.into());
|
||||
map.insert("port".into(), 7899.into());
|
||||
@@ -32,16 +51,24 @@ impl IClashTemp {
|
||||
map.insert("mode".into(), "rule".into());
|
||||
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||
map.insert("secret".into(), "".into());
|
||||
map.insert("tun".into(), tun.into());
|
||||
|
||||
Self(map)
|
||||
}
|
||||
|
||||
fn guard(mut config: Mapping) -> Mapping {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let redir_port = Self::guard_redir_port(&config);
|
||||
#[cfg(target_os = "linux")]
|
||||
let tproxy_port = Self::guard_tproxy_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);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
config.insert("redir-port".into(), redir_port.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
config.insert("tproxy-port".into(), tproxy_port.into());
|
||||
config.insert("mixed-port".into(), mixed_port.into());
|
||||
config.insert("socks-port".into(), socks_port.into());
|
||||
config.insert("port".into(), port.into());
|
||||
@@ -93,6 +120,37 @@ impl IClashTemp {
|
||||
}),
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("redir-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(7895);
|
||||
if port == 0 {
|
||||
port = 7895;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("tproxy-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(7896);
|
||||
if port == 0 {
|
||||
port = 7896;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
|
||||
@@ -13,3 +13,8 @@ pub use self::prfitem::*;
|
||||
pub use self::profiles::*;
|
||||
pub use self::runtime::*;
|
||||
pub use self::verge::*;
|
||||
|
||||
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
|
||||
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
|
||||
}
|
||||
"#;
|
||||
|
||||
@@ -46,6 +46,10 @@ pub struct PrfItem {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub option: Option<PrfOption>,
|
||||
|
||||
/// profile web page url
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub home: Option<String>,
|
||||
|
||||
/// the file data
|
||||
#[serde(skip)]
|
||||
pub file_data: Option<String>,
|
||||
@@ -84,6 +88,12 @@ pub struct PrfOption {
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub update_interval: Option<u64>,
|
||||
|
||||
/// for `remote` profile
|
||||
/// disable certificate validation
|
||||
/// default is `false`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub danger_accept_invalid_certs: Option<bool>,
|
||||
}
|
||||
|
||||
impl PrfOption {
|
||||
@@ -93,6 +103,7 @@ impl PrfOption {
|
||||
a.user_agent = b.user_agent.or(a.user_agent);
|
||||
a.with_proxy = b.with_proxy.or(a.with_proxy);
|
||||
a.self_proxy = b.self_proxy.or(a.self_proxy);
|
||||
a.danger_accept_invalid_certs = b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs);
|
||||
a.update_interval = b.update_interval.or(a.update_interval);
|
||||
Some(a)
|
||||
}
|
||||
@@ -154,6 +165,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
home: None,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||
})
|
||||
@@ -170,6 +182,7 @@ impl PrfItem {
|
||||
let opt_ref = option.as_ref();
|
||||
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
|
||||
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
|
||||
let accept_invalid_certs = opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
|
||||
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
|
||||
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
||||
|
||||
@@ -216,6 +229,7 @@ impl PrfItem {
|
||||
None => "clash-verge/unknown".to_string(),
|
||||
};
|
||||
|
||||
builder = builder.danger_accept_invalid_certs(accept_invalid_certs);
|
||||
builder = builder.user_agent(user_agent.unwrap_or(version));
|
||||
|
||||
let resp = builder.build()?.get(url).send().await?;
|
||||
@@ -282,6 +296,14 @@ impl PrfItem {
|
||||
},
|
||||
};
|
||||
|
||||
let home = match header.get("profile-web-page-url") {
|
||||
Some(value) => {
|
||||
let str_value = value.to_str().unwrap_or("");
|
||||
Some(str_value.to_string())
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let uid = help::get_uid("r");
|
||||
let file = format!("{uid}.yaml");
|
||||
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||
@@ -308,6 +330,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra,
|
||||
option,
|
||||
home,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(data.into()),
|
||||
})
|
||||
@@ -329,6 +352,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
home: None,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_MERGE.into()),
|
||||
})
|
||||
@@ -347,6 +371,7 @@ impl PrfItem {
|
||||
desc: Some(desc),
|
||||
file: Some(file),
|
||||
url: None,
|
||||
home: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
|
||||
@@ -211,7 +211,7 @@ impl IProfiles {
|
||||
if each.uid == some_uid {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
|
||||
each.home = item.home;
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::enhance::field::use_keys;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct IRuntime {
|
||||
pub config: Option<Mapping>,
|
||||
@@ -16,7 +16,7 @@ impl IRuntime {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
// 这里只更改 allow-lan | ipv6 | log-level
|
||||
// 这里只更改 allow-lan | ipv6 | log-level | tun
|
||||
pub fn patch_config(&mut self, patch: Mapping) {
|
||||
if let Some(config) = self.config.as_mut() {
|
||||
["allow-lan", "ipv6", "log-level"]
|
||||
@@ -26,6 +26,20 @@ impl IRuntime {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::config::DEFAULT_PAC;
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::Result;
|
||||
use log::LevelFilter;
|
||||
@@ -36,6 +37,25 @@ pub struct IVerge {
|
||||
/// show memory info (only for Clash Meta)
|
||||
pub enable_memory_usage: Option<bool>,
|
||||
|
||||
/// enable group icon
|
||||
pub enable_group_icon: Option<bool>,
|
||||
|
||||
/// common tray icon
|
||||
pub common_tray_icon: Option<bool>,
|
||||
|
||||
/// tray icon
|
||||
#[cfg(target_os = "macos")]
|
||||
pub tray_icon: Option<String>,
|
||||
|
||||
/// menu icon
|
||||
pub menu_icon: Option<String>,
|
||||
|
||||
/// sysproxy tray icon
|
||||
pub sysproxy_tray_icon: Option<bool>,
|
||||
|
||||
/// tun tray icon
|
||||
pub tun_tray_icon: Option<bool>,
|
||||
|
||||
/// clash tun mode
|
||||
pub enable_tun_mode: Option<bool>,
|
||||
|
||||
@@ -61,6 +81,12 @@ pub struct IVerge {
|
||||
/// proxy guard duration
|
||||
pub proxy_guard_duration: Option<u64>,
|
||||
|
||||
/// use pac mode
|
||||
pub proxy_auto_config: Option<bool>,
|
||||
|
||||
/// pac script content
|
||||
pub pac_file_content: Option<String>,
|
||||
|
||||
/// theme setting
|
||||
pub theme_setting: Option<IVergeTheme>,
|
||||
|
||||
@@ -78,9 +104,15 @@ pub struct IVerge {
|
||||
/// 切换代理时自动关闭连接
|
||||
pub auto_close_connection: Option<bool>,
|
||||
|
||||
/// 是否自动检查更新
|
||||
pub auto_check_update: Option<bool>,
|
||||
|
||||
/// 默认的延迟测试连接
|
||||
pub default_latency_test: Option<String>,
|
||||
|
||||
/// 默认的延迟测试超时时间
|
||||
pub default_latency_timeout: Option<i32>,
|
||||
|
||||
/// 是否使用内部的脚本支持,默认为真
|
||||
pub enable_builtin_enhanced: Option<bool>,
|
||||
|
||||
@@ -98,15 +130,35 @@ pub struct IVerge {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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>,
|
||||
|
||||
/// verge mixed port 用于覆盖 clash 的 mixed port
|
||||
/// verge 的各种 port 用于覆盖 clash 的各种 port
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub verge_redir_port: Option<u16>,
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub verge_redir_enabled: Option<bool>,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub verge_tproxy_port: Option<u16>,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub verge_tproxy_enabled: Option<bool>,
|
||||
|
||||
pub verge_mixed_port: Option<u16>,
|
||||
|
||||
pub verge_socks_port: Option<u16>,
|
||||
|
||||
pub verge_socks_enabled: Option<bool>,
|
||||
|
||||
pub verge_port: Option<u16>,
|
||||
|
||||
pub verge_http_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
@@ -156,16 +208,36 @@ impl IVerge {
|
||||
start_page: Some("/".into()),
|
||||
traffic_graph: Some(true),
|
||||
enable_memory_usage: Some(true),
|
||||
enable_group_icon: Some(true),
|
||||
#[cfg(target_os = "macos")]
|
||||
tray_icon: Some("monochrome".into()),
|
||||
menu_icon: Some("monochrome".into()),
|
||||
common_tray_icon: Some(false),
|
||||
sysproxy_tray_icon: Some(false),
|
||||
tun_tray_icon: Some(false),
|
||||
enable_auto_launch: Some(false),
|
||||
enable_silent_start: Some(false),
|
||||
enable_system_proxy: Some(false),
|
||||
proxy_auto_config: Some(false),
|
||||
pac_file_content: Some(DEFAULT_PAC.into()),
|
||||
enable_random_port: Some(false),
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
verge_redir_port: Some(7895),
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
verge_redir_enabled: Some(true),
|
||||
#[cfg(target_os = "linux")]
|
||||
verge_tproxy_port: Some(7896),
|
||||
#[cfg(target_os = "linux")]
|
||||
verge_tproxy_enabled: Some(true),
|
||||
verge_mixed_port: Some(7897),
|
||||
verge_socks_port: Some(7898),
|
||||
verge_socks_enabled: Some(true),
|
||||
verge_port: Some(7899),
|
||||
verge_http_enabled: Some(true),
|
||||
enable_proxy_guard: Some(false),
|
||||
proxy_guard_duration: Some(30),
|
||||
auto_close_connection: Some(true),
|
||||
auto_check_update: Some(true),
|
||||
enable_builtin_enhanced: Some(true),
|
||||
auto_log_clean: Some(3),
|
||||
..Self::default()
|
||||
@@ -197,19 +269,38 @@ impl IVerge {
|
||||
patch!(startup_script);
|
||||
patch!(traffic_graph);
|
||||
patch!(enable_memory_usage);
|
||||
patch!(enable_group_icon);
|
||||
#[cfg(target_os = "macos")]
|
||||
patch!(tray_icon);
|
||||
patch!(menu_icon);
|
||||
patch!(common_tray_icon);
|
||||
patch!(sysproxy_tray_icon);
|
||||
patch!(tun_tray_icon);
|
||||
|
||||
patch!(enable_tun_mode);
|
||||
patch!(enable_service_mode);
|
||||
patch!(enable_auto_launch);
|
||||
patch!(enable_silent_start);
|
||||
patch!(enable_random_port);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
patch!(verge_redir_port);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
patch!(verge_redir_enabled);
|
||||
#[cfg(target_os = "linux")]
|
||||
patch!(verge_tproxy_port);
|
||||
#[cfg(target_os = "linux")]
|
||||
patch!(verge_tproxy_enabled);
|
||||
patch!(verge_mixed_port);
|
||||
patch!(verge_socks_port);
|
||||
patch!(verge_socks_enabled);
|
||||
patch!(verge_port);
|
||||
patch!(verge_http_enabled);
|
||||
patch!(enable_system_proxy);
|
||||
patch!(enable_proxy_guard);
|
||||
patch!(system_proxy_bypass);
|
||||
patch!(proxy_guard_duration);
|
||||
patch!(proxy_auto_config);
|
||||
patch!(pac_file_content);
|
||||
|
||||
patch!(theme_setting);
|
||||
patch!(web_ui_list);
|
||||
@@ -217,12 +308,15 @@ impl IVerge {
|
||||
patch!(hotkeys);
|
||||
|
||||
patch!(auto_close_connection);
|
||||
patch!(auto_check_update);
|
||||
patch!(default_latency_test);
|
||||
patch!(default_latency_timeout);
|
||||
patch!(enable_builtin_enhanced);
|
||||
patch!(proxy_layout_column);
|
||||
patch!(test_list);
|
||||
patch!(auto_log_clean);
|
||||
patch!(window_size_position);
|
||||
patch!(window_is_maximized);
|
||||
}
|
||||
|
||||
/// 在初始化前尝试拿到单例端口的值
|
||||
|
||||
@@ -44,7 +44,11 @@ pub struct DelayRes {
|
||||
|
||||
/// 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 = 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
|
||||
.get(&url)
|
||||
.headers(headers)
|
||||
.query(&[("timeout", "10000"), ("url", &test_url)]);
|
||||
.query(&[("timeout", &format!("{timeout}")), ("url", &test_url)]);
|
||||
let response = builder.send().await?;
|
||||
|
||||
Ok(response.json::<DelayRes>().await?)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::service;
|
||||
use super::{clash_api, logger::Logger};
|
||||
use crate::log_err;
|
||||
use crate::{config::*, utils::dirs};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use serde_yaml::Mapping;
|
||||
use std::{fs, io::Write, sync::Arc, time::Duration};
|
||||
use sysinfo::{Pid, System};
|
||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||
@@ -93,10 +95,9 @@ impl CoreManager {
|
||||
None => false,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if *self.use_service_mode.lock() {
|
||||
log::debug!(target: "app", "stop the core by service");
|
||||
log_err!(super::win_service::stop_core_by_service().await);
|
||||
log_err!(service::stop_core_by_service().await);
|
||||
should_kill = true;
|
||||
}
|
||||
|
||||
@@ -105,32 +106,27 @@ impl CoreManager {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use super::win_service;
|
||||
// 服务模式
|
||||
let enable = { Config::verge().latest().enable_service_mode };
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
// 服务模式
|
||||
let enable = { Config::verge().latest().enable_service_mode };
|
||||
let enable = enable.unwrap_or(false);
|
||||
*self.use_service_mode.lock() = enable;
|
||||
|
||||
*self.use_service_mode.lock() = enable;
|
||||
if enable {
|
||||
// 服务模式启动失败就直接运行sidecar
|
||||
log::debug!(target: "app", "try to run core in service mode");
|
||||
|
||||
if enable {
|
||||
// 服务模式启动失败就直接运行sidecar
|
||||
log::debug!(target: "app", "try to run core in service mode");
|
||||
|
||||
match (|| async {
|
||||
win_service::check_service().await?;
|
||||
win_service::run_core_by_service(&config_path).await
|
||||
})()
|
||||
.await
|
||||
{
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
// 修改这个值,免得stop出错
|
||||
*self.use_service_mode.lock() = false;
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
match (|| async {
|
||||
service::check_service().await?;
|
||||
service::run_core_by_service(&config_path).await
|
||||
})()
|
||||
.await
|
||||
{
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
// 修改这个值,免得stop出错
|
||||
*self.use_service_mode.lock() = false;
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,10 +140,9 @@ impl CoreManager {
|
||||
|
||||
let config_path = dirs::path_to_str(&config_path)?;
|
||||
|
||||
// fix #212
|
||||
let args = match clash_core.as_str() {
|
||||
"clash-meta" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||
"clash-meta-alpha" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||
"clash-meta" => vec!["-d", app_dir, "-f", config_path],
|
||||
"clash-meta-alpha" => vec!["-d", app_dir, "-f", config_path],
|
||||
_ => vec!["-d", app_dir, "-f", config_path],
|
||||
};
|
||||
|
||||
@@ -206,7 +201,6 @@ impl CoreManager {
|
||||
/// 重启内核
|
||||
pub fn recover_core(&'static self) -> Result<()> {
|
||||
// 服务模式不管
|
||||
#[cfg(target_os = "windows")]
|
||||
if *self.use_service_mode.lock() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -239,11 +233,20 @@ impl CoreManager {
|
||||
|
||||
/// 停止核心运行
|
||||
pub fn stop_core(&self) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
// 关闭tun模式
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let mut disable = Mapping::new();
|
||||
let mut tun = Mapping::new();
|
||||
tun.insert("enable".into(), false.into());
|
||||
disable.insert("tun".into(), tun.into());
|
||||
log::debug!(target: "app", "disable tun mode");
|
||||
let _ = clash_api::patch_configs(&disable).await;
|
||||
});
|
||||
|
||||
if *self.use_service_mode.lock() {
|
||||
log::debug!(target: "app", "stop the core by service");
|
||||
tauri::async_runtime::block_on(async move {
|
||||
log_err!(super::win_service::stop_core_by_service().await);
|
||||
log_err!(service::stop_core_by_service().await);
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -65,17 +65,12 @@ impl Hotkey {
|
||||
}
|
||||
|
||||
let f = match func.trim() {
|
||||
"open_dashboard" => feat::open_dashboard,
|
||||
"open_or_close_dashboard" => feat::open_or_close_dashboard,
|
||||
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
|
||||
"clash_mode_global" => || feat::change_clash_mode("global".into()),
|
||||
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
|
||||
"clash_mode_script" => || feat::change_clash_mode("script".into()),
|
||||
"toggle_system_proxy" => feat::toggle_system_proxy,
|
||||
"enable_system_proxy" => feat::enable_system_proxy,
|
||||
"disable_system_proxy" => feat::disable_system_proxy,
|
||||
"toggle_tun_mode" => feat::toggle_tun_mode,
|
||||
"enable_tun_mode" => feat::enable_tun_mode,
|
||||
"disable_tun_mode" => feat::disable_tun_mode,
|
||||
|
||||
_ => bail!("invalid function \"{func}\""),
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn grant_permission(core: String) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let output = {
|
||||
let path = path.replace(' ', "\\ "); // 避免路径中有空格
|
||||
let shell = format!("setcap cap_net_bind_service,cap_net_admin=+ep {path}");
|
||||
let shell = format!("setcap cap_net_bind_service,cap_net_admin,cap_dac_override=+ep {path}");
|
||||
|
||||
let sudo = match Command::new("which").arg("pkexec").output() {
|
||||
Ok(output) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ pub mod manager;
|
||||
pub mod sysopt;
|
||||
pub mod timer;
|
||||
pub mod tray;
|
||||
pub mod win_service;
|
||||
pub mod service;
|
||||
pub mod win_uwp;
|
||||
|
||||
pub use self::core::*;
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::utils::dirs;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use std::{env::current_exe, process::Command as StdCommand};
|
||||
use tokio::time::sleep;
|
||||
|
||||
// Windows only
|
||||
|
||||
const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
@@ -32,7 +29,13 @@ pub struct JsonResponse {
|
||||
|
||||
/// Install the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
///
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn install_service() -> Result<()> {
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let install_path = binary_path.with_file_name("install-service.exe");
|
||||
|
||||
@@ -60,9 +63,69 @@ pub async fn install_service() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn install_service() -> Result<()> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let installer_path = binary_path.with_file_name("install-service");
|
||||
|
||||
if !installer_path.exists() {
|
||||
bail!("installer not found");
|
||||
}
|
||||
|
||||
let elevator = crate::utils::unix_helper::linux_elevator();
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new(installer_path).status()?,
|
||||
_ => StdCommand::new(elevator)
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(installer_path)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn install_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path()?;
|
||||
let installer_path = binary_path.with_file_name("install-service");
|
||||
|
||||
if !installer_path.exists() {
|
||||
bail!("installer not found");
|
||||
}
|
||||
let shell = installer_path.to_string_lossy().replace(" ", "\\\\ ");
|
||||
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
|
||||
|
||||
let status = StdCommand::new("osascript")
|
||||
.args(vec!["-e", &command])
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Uninstall the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
||||
|
||||
@@ -90,6 +153,63 @@ pub async fn uninstall_service() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
||||
|
||||
if !uninstaller_path.exists() {
|
||||
bail!("uninstaller not found");
|
||||
}
|
||||
|
||||
let elevator = crate::utils::unix_helper::linux_elevator();
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new(uninstaller_path).status()?,
|
||||
_ => StdCommand::new(elevator)
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(uninstaller_path)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path()?;
|
||||
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
||||
|
||||
if !uninstaller_path.exists() {
|
||||
bail!("uninstaller not found");
|
||||
}
|
||||
|
||||
let shell = uninstaller_path.to_string_lossy().replace(" ", "\\\\ ");
|
||||
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
|
||||
|
||||
let status = StdCommand::new("osascript")
|
||||
.args(vec!["-e", &command])
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// check the windows service status
|
||||
pub async fn check_service() -> Result<JsonResponse> {
|
||||
let url = format!("{SERVICE_URL}/get_clash");
|
||||
@@ -119,7 +239,8 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("clash".into());
|
||||
|
||||
let clash_bin = format!("{clash_core}.exe");
|
||||
let bin_ext = if cfg!(windows) { ".exe" } else { "" };
|
||||
let clash_bin = format!("{clash_core}{bin_ext}");
|
||||
let bin_path = current_exe()?.with_file_name(clash_bin);
|
||||
let bin_path = dirs::path_to_str(&bin_path)?;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use crate::{config::Config, log_err};
|
||||
use crate::{
|
||||
config::{Config, IVerge},
|
||||
log_err,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::env::current_exe;
|
||||
use std::sync::Arc;
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
|
||||
use sysproxy::{Autoproxy, Sysproxy};
|
||||
use tauri::async_runtime::Mutex as TokioMutex;
|
||||
|
||||
pub struct Sysopt {
|
||||
/// current system proxy setting
|
||||
@@ -15,6 +19,13 @@ pub struct Sysopt {
|
||||
/// recover it when exit
|
||||
old_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
|
||||
|
||||
/// current auto proxy setting
|
||||
cur_autoproxy: Arc<Mutex<Option<Autoproxy>>>,
|
||||
|
||||
/// record the original auto proxy
|
||||
/// recover it when exit
|
||||
old_autoproxy: Arc<Mutex<Option<Autoproxy>>>,
|
||||
|
||||
/// helps to auto launch the app
|
||||
auto_launch: Arc<Mutex<Option<AutoLaunch>>>,
|
||||
|
||||
@@ -23,7 +34,7 @@ pub struct Sysopt {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;<local>";
|
||||
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
|
||||
#[cfg(target_os = "linux")]
|
||||
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1";
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -37,6 +48,8 @@ impl Sysopt {
|
||||
SYSOPT.get_or_init(|| Sysopt {
|
||||
cur_sysproxy: Arc::new(Mutex::new(None)),
|
||||
old_sysproxy: Arc::new(Mutex::new(None)),
|
||||
cur_autoproxy: Arc::new(Mutex::new(None)),
|
||||
old_autoproxy: Arc::new(Mutex::new(None)),
|
||||
auto_launch: Arc::new(Mutex::new(None)),
|
||||
guard_state: Arc::new(TokioMutex::new(false)),
|
||||
})
|
||||
@@ -48,29 +61,58 @@ impl Sysopt {
|
||||
.latest()
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||
let pac_port = IVerge::get_singleton_port();
|
||||
|
||||
let (enable, bypass) = {
|
||||
let (enable, bypass, pac) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.unwrap_or(false),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
verge.proxy_auto_config.unwrap_or(false),
|
||||
)
|
||||
};
|
||||
|
||||
let current = Sysproxy {
|
||||
let mut sys = Sysproxy {
|
||||
enable,
|
||||
host: String::from("127.0.0.1"),
|
||||
port,
|
||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||
bypass: match bypass {
|
||||
Some(bypass) => {
|
||||
if bypass.is_empty() {
|
||||
DEFAULT_BYPASS.into()
|
||||
} else {
|
||||
bypass
|
||||
}
|
||||
}
|
||||
None => DEFAULT_BYPASS.into(),
|
||||
},
|
||||
};
|
||||
|
||||
if enable {
|
||||
let mut auto = Autoproxy {
|
||||
enable,
|
||||
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
|
||||
};
|
||||
if pac {
|
||||
sys.enable = false;
|
||||
let old = Sysproxy::get_system_proxy().ok();
|
||||
current.set_system_proxy()?;
|
||||
|
||||
sys.set_system_proxy()?;
|
||||
*self.old_sysproxy.lock() = old;
|
||||
*self.cur_sysproxy.lock() = Some(current);
|
||||
*self.cur_sysproxy.lock() = Some(sys);
|
||||
|
||||
let old = Autoproxy::get_auto_proxy().ok();
|
||||
auto.set_auto_proxy()?;
|
||||
*self.old_autoproxy.lock() = old;
|
||||
*self.cur_autoproxy.lock() = Some(auto);
|
||||
} else {
|
||||
auto.enable = false;
|
||||
let old = Autoproxy::get_auto_proxy().ok();
|
||||
auto.set_auto_proxy()?;
|
||||
*self.old_autoproxy.lock() = old;
|
||||
*self.cur_autoproxy.lock() = Some(auto);
|
||||
|
||||
let old = Sysproxy::get_system_proxy().ok();
|
||||
sys.set_system_proxy()?;
|
||||
*self.old_sysproxy.lock() = old;
|
||||
*self.cur_sysproxy.lock() = Some(sys);
|
||||
}
|
||||
|
||||
// run the system proxy guard
|
||||
@@ -82,34 +124,68 @@ impl Sysopt {
|
||||
pub fn update_sysproxy(&self) -> Result<()> {
|
||||
let mut cur_sysproxy = self.cur_sysproxy.lock();
|
||||
let old_sysproxy = self.old_sysproxy.lock();
|
||||
let mut cur_autoproxy = self.cur_autoproxy.lock();
|
||||
let old_autoproxy = self.old_autoproxy.lock();
|
||||
|
||||
if cur_sysproxy.is_none() || old_sysproxy.is_none() {
|
||||
drop(cur_sysproxy);
|
||||
drop(old_sysproxy);
|
||||
return self.init_sysproxy();
|
||||
}
|
||||
|
||||
let (enable, bypass) = {
|
||||
let (enable, bypass, pac) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.enable_system_proxy.unwrap_or(false),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
verge.proxy_auto_config.unwrap_or(false),
|
||||
)
|
||||
};
|
||||
let mut sysproxy = cur_sysproxy.take().unwrap();
|
||||
|
||||
sysproxy.enable = enable;
|
||||
sysproxy.bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
|
||||
|
||||
if pac {
|
||||
if cur_autoproxy.is_none() || old_autoproxy.is_none() {
|
||||
drop(cur_autoproxy);
|
||||
drop(old_autoproxy);
|
||||
return self.init_sysproxy();
|
||||
}
|
||||
} else {
|
||||
if cur_sysproxy.is_none() || old_sysproxy.is_none() {
|
||||
drop(cur_sysproxy);
|
||||
drop(old_sysproxy);
|
||||
return self.init_sysproxy();
|
||||
}
|
||||
}
|
||||
let port = Config::verge()
|
||||
.latest()
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||
let pac_port = IVerge::get_singleton_port();
|
||||
|
||||
let mut sysproxy = cur_sysproxy.take().unwrap();
|
||||
sysproxy.bypass = match bypass {
|
||||
Some(bypass) => {
|
||||
if bypass.is_empty() {
|
||||
DEFAULT_BYPASS.into()
|
||||
} else {
|
||||
bypass
|
||||
}
|
||||
}
|
||||
None => DEFAULT_BYPASS.into(),
|
||||
};
|
||||
sysproxy.port = port;
|
||||
|
||||
sysproxy.set_system_proxy()?;
|
||||
*cur_sysproxy = Some(sysproxy);
|
||||
let mut autoproxy = cur_autoproxy.take().unwrap();
|
||||
autoproxy.url = format!("http://127.0.0.1:{pac_port}/commands/pac");
|
||||
|
||||
if pac {
|
||||
sysproxy.enable = false;
|
||||
sysproxy.set_system_proxy()?;
|
||||
*cur_sysproxy = Some(sysproxy);
|
||||
autoproxy.enable = enable;
|
||||
autoproxy.set_auto_proxy()?;
|
||||
*cur_autoproxy = Some(autoproxy);
|
||||
} else {
|
||||
autoproxy.enable = false;
|
||||
autoproxy.set_auto_proxy()?;
|
||||
*cur_autoproxy = Some(autoproxy);
|
||||
sysproxy.enable = enable;
|
||||
sysproxy.set_system_proxy()?;
|
||||
*cur_sysproxy = Some(sysproxy);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -118,8 +194,11 @@ impl Sysopt {
|
||||
pub fn reset_sysproxy(&self) -> Result<()> {
|
||||
let mut cur_sysproxy = self.cur_sysproxy.lock();
|
||||
let mut old_sysproxy = self.old_sysproxy.lock();
|
||||
let mut cur_autoproxy = self.cur_autoproxy.lock();
|
||||
let mut old_autoproxy = self.old_autoproxy.lock();
|
||||
|
||||
let cur_sysproxy = cur_sysproxy.take();
|
||||
let cur_autoproxy = cur_autoproxy.take();
|
||||
|
||||
if let Some(mut old) = old_sysproxy.take() {
|
||||
// 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置
|
||||
@@ -143,16 +222,35 @@ impl Sysopt {
|
||||
log::info!(target: "app", "reset proxy with no action");
|
||||
}
|
||||
|
||||
if let Some(mut old) = old_autoproxy.take() {
|
||||
// 如果原代理和当前代理 URL一致,就disable关闭,否则就恢复原代理设置
|
||||
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
|
||||
let url_same = cur_autoproxy.map_or(true, |cur| old.url == cur.url);
|
||||
|
||||
if old.enable && url_same {
|
||||
old.enable = false;
|
||||
log::info!(target: "app", "reset proxy by disabling the original proxy");
|
||||
} else {
|
||||
log::info!(target: "app", "reset proxy to the original proxy");
|
||||
}
|
||||
|
||||
old.set_auto_proxy()?;
|
||||
} else if let Some(mut cur @ Autoproxy { enable: true, .. }) = cur_autoproxy {
|
||||
// 没有原代理,就按现在的代理设置disable即可
|
||||
log::info!(target: "app", "reset proxy by disabling the current proxy");
|
||||
cur.enable = false;
|
||||
cur.set_auto_proxy()?;
|
||||
} else {
|
||||
log::info!(target: "app", "reset proxy with no action");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// init the auto launch
|
||||
pub fn init_launch(&self) -> Result<()> {
|
||||
let enable = { Config::verge().latest().enable_auto_launch };
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
// let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_name = app_exe
|
||||
.file_stem()
|
||||
.and_then(|f| f.to_str())
|
||||
@@ -204,28 +302,6 @@ impl Sysopt {
|
||||
.set_app_path(&app_path)
|
||||
.build()?;
|
||||
|
||||
// 避免在开发时将自启动关了
|
||||
#[cfg(feature = "verge-dev")]
|
||||
if !enable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if enable && !auto.is_enabled().unwrap_or(false) {
|
||||
// 避免重复设置登录项
|
||||
let _ = auto.disable();
|
||||
auto.enable()?;
|
||||
} else if !enable {
|
||||
let _ = auto.disable();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
if enable {
|
||||
auto.enable()?;
|
||||
}
|
||||
|
||||
*self.auto_launch.lock() = Some(auto);
|
||||
|
||||
Ok(())
|
||||
@@ -273,7 +349,7 @@ impl Sysopt {
|
||||
loop {
|
||||
sleep(Duration::from_secs(wait_secs)).await;
|
||||
|
||||
let (enable, guard, guard_duration, bypass) = {
|
||||
let (enable, guard, guard_duration, bypass, pac) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
@@ -281,6 +357,7 @@ impl Sysopt {
|
||||
verge.enable_proxy_guard.unwrap_or(false),
|
||||
verge.proxy_guard_duration.unwrap_or(10),
|
||||
verge.system_proxy_bypass.clone(),
|
||||
verge.proxy_auto_config.unwrap_or(false),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -300,15 +377,32 @@ impl Sysopt {
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port())
|
||||
};
|
||||
let pac_port = IVerge::get_singleton_port();
|
||||
if pac {
|
||||
let autoproxy = Autoproxy {
|
||||
enable: true,
|
||||
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
|
||||
};
|
||||
log_err!(autoproxy.set_auto_proxy());
|
||||
} else {
|
||||
let sysproxy = Sysproxy {
|
||||
enable: true,
|
||||
host: "127.0.0.1".into(),
|
||||
port,
|
||||
bypass: match bypass {
|
||||
Some(bypass) => {
|
||||
if bypass.is_empty() {
|
||||
DEFAULT_BYPASS.into()
|
||||
} else {
|
||||
bypass
|
||||
}
|
||||
}
|
||||
None => DEFAULT_BYPASS.into(),
|
||||
},
|
||||
};
|
||||
|
||||
let sysproxy = Sysproxy {
|
||||
enable: true,
|
||||
host: "127.0.0.1".into(),
|
||||
port,
|
||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||
};
|
||||
|
||||
log_err!(sysproxy.set_system_proxy());
|
||||
log_err!(sysproxy.set_system_proxy());
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = guard_state.lock().await;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use crate::{cmds, config::Config, feat, utils::resolve};
|
||||
use crate::{
|
||||
cmds,
|
||||
config::Config,
|
||||
feat,
|
||||
utils::{dirs, resolve},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use tauri::{
|
||||
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
||||
@@ -125,30 +130,125 @@ impl Tray {
|
||||
let _ = tray.get_item("global_mode").set_selected(mode == "global");
|
||||
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
match mode.as_str() {
|
||||
"rule" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode ✔", "规则模式 ✔"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode", "全局模式"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode", "直连模式"));
|
||||
}
|
||||
"global" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode", "规则模式"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode ✔", "全局模式 ✔"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode", "直连模式"));
|
||||
}
|
||||
"direct" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode", "规则模式"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode", "全局模式"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode ✔", "直连模式 ✔"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
|
||||
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
|
||||
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
|
||||
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
|
||||
#[cfg(target_os = "macos")]
|
||||
match tray_icon.as_str() {
|
||||
"monochrome" => {
|
||||
let _ = tray.set_icon_as_template(true);
|
||||
}
|
||||
"colorful" => {
|
||||
let _ = tray.set_icon_as_template(false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let mut indication_icon = if *system_proxy {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let icon = include_bytes!("../../icons/tray-icon-sys.png").to_vec();
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"monochrome" => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||
"colorful" => include_bytes!("../../icons/tray-icon-sys.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||
};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let mut icon = include_bytes!("../../icons/tray-icon-sys.ico").to_vec();
|
||||
|
||||
if *sysproxy_tray_icon {
|
||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||
let png_path = icon_dir_path.join("sysproxy.png");
|
||||
let ico_path = icon_dir_path.join("sysproxy.ico");
|
||||
if ico_path.exists() {
|
||||
icon = std::fs::read(ico_path).unwrap();
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
}
|
||||
icon
|
||||
} else {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let icon = include_bytes!("../../icons/tray-icon.png").to_vec();
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"monochrome" => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
|
||||
"colorful" => include_bytes!("../../icons/tray-icon.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
|
||||
};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let mut icon = include_bytes!("../../icons/tray-icon.ico").to_vec();
|
||||
if *common_tray_icon {
|
||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||
let png_path = icon_dir_path.join("common.png");
|
||||
let ico_path = icon_dir_path.join("common.ico");
|
||||
if ico_path.exists() {
|
||||
icon = std::fs::read(ico_path).unwrap();
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
}
|
||||
icon
|
||||
};
|
||||
|
||||
if *tun_mode {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
|
||||
#[cfg(target_os = "macos")]
|
||||
let icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"monochrome" => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||
"colorful" => include_bytes!("../../icons/tray-icon-tun.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||
};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let mut icon = include_bytes!("../../icons/tray-icon-tun.ico").to_vec();
|
||||
if *tun_tray_icon {
|
||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||
let png_path = icon_dir_path.join("tun.png");
|
||||
let ico_path = icon_dir_path.join("tun.ico");
|
||||
if ico_path.exists() {
|
||||
icon = std::fs::read(ico_path).unwrap();
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
}
|
||||
indication_icon = icon
|
||||
}
|
||||
|
||||
@@ -156,6 +256,27 @@ impl Tray {
|
||||
|
||||
let _ = tray.get_item("system_proxy").set_selected(*system_proxy);
|
||||
let _ = tray.get_item("tun_mode").set_selected(*tun_mode);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if *system_proxy {
|
||||
let _ = tray
|
||||
.get_item("system_proxy")
|
||||
.set_title(t!("System Proxy ✔", "系统代理 ✔"));
|
||||
} else {
|
||||
let _ = tray
|
||||
.get_item("system_proxy")
|
||||
.set_title(t!("System Proxy", "系统代理"));
|
||||
}
|
||||
if *tun_mode {
|
||||
let _ = tray
|
||||
.get_item("tun_mode")
|
||||
.set_title(t!("TUN Mode ✔", "Tun 模式 ✔"));
|
||||
} else {
|
||||
let _ = tray
|
||||
.get_item("tun_mode")
|
||||
.set_title(t!("TUN Mode", "Tun 模式"));
|
||||
}
|
||||
}
|
||||
|
||||
let switch_map = {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
@@ -164,18 +285,30 @@ impl Tray {
|
||||
map
|
||||
};
|
||||
|
||||
let mut current_profile_name = "None".to_string();
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
if let Some(current_profile_uid) = profiles.get_current() {
|
||||
let current_profile = profiles.get_item(¤t_profile_uid);
|
||||
current_profile_name = match ¤t_profile.unwrap().name {
|
||||
Some(profile_name) => profile_name.to_string(),
|
||||
None => current_profile_name,
|
||||
};
|
||||
};
|
||||
let _ = tray.set_tooltip(&format!(
|
||||
"Clash Verge {version}\n{}: {}\n{}: {}",
|
||||
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
||||
t!("System Proxy", "系统代理"),
|
||||
switch_map[system_proxy],
|
||||
t!("TUN Mode", "Tun 模式"),
|
||||
switch_map[tun_mode]
|
||||
switch_map[tun_mode],
|
||||
t!("Curent Profile", "当前订阅"),
|
||||
current_profile_name
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_left_click(app_handle: &AppHandle) {
|
||||
pub fn on_click(app_handle: &AppHandle) {
|
||||
let tray_event = { Config::verge().latest().tray_event.clone() };
|
||||
let tray_event = tray_event.unwrap_or("main_window".into());
|
||||
match tray_event.as_str() {
|
||||
@@ -188,7 +321,10 @@ impl Tray {
|
||||
|
||||
pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) {
|
||||
match event {
|
||||
SystemTrayEvent::LeftClick { .. } => Tray::on_left_click(app_handle),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
SystemTrayEvent::LeftClick { .. } => Tray::on_click(app_handle),
|
||||
#[cfg(target_os = "macos")]
|
||||
SystemTrayEvent::RightClick { .. } => Tray::on_click(app_handle),
|
||||
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
|
||||
mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
|
||||
let mode = &mode[0..mode.len() - 5];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
function main(params) {
|
||||
if (params.mode === "script") {
|
||||
params.mode = "rule";
|
||||
function main(config) {
|
||||
if (config.mode === "script") {
|
||||
config.mode = "rule";
|
||||
}
|
||||
return params;
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
function main(params) {
|
||||
if (Array.isArray(params.proxies)) {
|
||||
params.proxies.forEach((p, i) => {
|
||||
function main(config) {
|
||||
if (Array.isArray(config.proxies)) {
|
||||
config.proxies.forEach((p, i) => {
|
||||
if (p.type === "hysteria" && typeof p.alpn === "string") {
|
||||
params.proxies[i].alpn = [p.alpn];
|
||||
config.proxies[i].alpn = [p.alpn];
|
||||
}
|
||||
});
|
||||
}
|
||||
return params;
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub const HANDLE_FIELDS: [&str; 9] = [
|
||||
pub const HANDLE_FIELDS: [&str; 11] = [
|
||||
"mode",
|
||||
"redir-port",
|
||||
"tproxy-port",
|
||||
"mixed-port",
|
||||
"socks-port",
|
||||
"port",
|
||||
|
||||
@@ -10,14 +10,26 @@ const MERGE_FIELDS: [&str; 6] = [
|
||||
"append-proxy-groups",
|
||||
];
|
||||
|
||||
pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
||||
// 直接覆盖原字段
|
||||
use_lowercase(merge.clone())
|
||||
.into_iter()
|
||||
.for_each(|(key, value)| {
|
||||
config.insert(key, value);
|
||||
});
|
||||
fn deep_merge(a: &mut Value, b: &Value) {
|
||||
match (a, b) {
|
||||
(&mut Value::Mapping(ref mut a), &Value::Mapping(ref b)) => {
|
||||
for (k, v) in b {
|
||||
deep_merge(a.entry(k.clone()).or_insert(Value::Null), v);
|
||||
}
|
||||
}
|
||||
(a, b) => *a = b.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_merge(merge: Mapping, config: Mapping) -> Mapping {
|
||||
let mut config = Value::from(config);
|
||||
let mut merge_without_append = use_lowercase(merge.clone());
|
||||
for key in MERGE_FIELDS {
|
||||
merge_without_append.remove(key).unwrap_or_default();
|
||||
}
|
||||
deep_merge(&mut config, &Value::from(merge_without_append));
|
||||
|
||||
let mut config = config.as_mapping().unwrap().clone();
|
||||
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
|
||||
let merge = use_filter(merge, &merge_list.collect());
|
||||
|
||||
@@ -48,7 +60,9 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
|
||||
}
|
||||
}
|
||||
|
||||
config.insert(key_val, Value::from(list));
|
||||
if !list.is_empty() {
|
||||
config.insert(key_val, Value::from(list));
|
||||
}
|
||||
});
|
||||
config
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod chain;
|
||||
mod field;
|
||||
pub mod field;
|
||||
mod merge;
|
||||
mod script;
|
||||
mod tun;
|
||||
@@ -22,15 +22,29 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
// config.yaml 的订阅
|
||||
let clash_config = { Config::clash().latest().0.clone() };
|
||||
|
||||
let (clash_core, enable_tun, enable_builtin) = {
|
||||
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled) = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
(
|
||||
verge.clash_core.clone(),
|
||||
verge.enable_tun_mode.unwrap_or(false),
|
||||
verge.enable_builtin_enhanced.unwrap_or(true),
|
||||
verge.verge_socks_enabled.unwrap_or(true),
|
||||
verge.verge_http_enabled.unwrap_or(true),
|
||||
)
|
||||
};
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let redir_enabled = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
verge.verge_redir_enabled.unwrap_or(true)
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
let tproxy_enabled = {
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
verge.verge_tproxy_enabled.unwrap_or(true)
|
||||
};
|
||||
|
||||
// 从profiles里拿东西
|
||||
let (mut config, chain) = {
|
||||
@@ -66,6 +80,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
match use_script(script, config.to_owned()) {
|
||||
Ok((res_config, res_logs)) => {
|
||||
exists_keys.extend(use_keys(&res_config));
|
||||
config = res_config;
|
||||
logs.extend(res_logs);
|
||||
}
|
||||
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||
@@ -77,7 +92,40 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
|
||||
// 合并默认的config
|
||||
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 {
|
||||
if key.as_str() == Some("socks-port") && !socks_enabled {
|
||||
config.remove("socks-port");
|
||||
continue;
|
||||
}
|
||||
if key.as_str() == Some("port") && !http_enabled {
|
||||
config.remove("port");
|
||||
continue;
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
if key.as_str() == Some("redir-port") && !redir_enabled {
|
||||
config.remove("redir-port");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if key.as_str() == Some("tproxy-port") && !tproxy_enabled {
|
||||
config.remove("tproxy-port");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
config.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 内建脚本最后跑
|
||||
|
||||
@@ -1,62 +1,75 @@
|
||||
use super::use_lowercase;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Error, Result};
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||
use rquickjs::{function::Func, Context, Runtime};
|
||||
use boa_engine::{native_function::NativeFunction, Context, JsValue, Source};
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut context = Context::default();
|
||||
|
||||
let runtime = Runtime::new().unwrap();
|
||||
let context = Context::full(&runtime).unwrap();
|
||||
let outputs = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
let copy_outputs = outputs.clone();
|
||||
let result = context.with(|ctx| -> Result<Mapping> {
|
||||
ctx.globals().set(
|
||||
"__verge_log__",
|
||||
Func::from(move |level: String, data: String| {
|
||||
let mut out = copy_outputs.lock().unwrap();
|
||||
out.push((level, data));
|
||||
}),
|
||||
)?;
|
||||
|
||||
ctx.eval(
|
||||
r#"var console = Object.freeze({
|
||||
unsafe {
|
||||
let _ = context.register_global_builtin_callable(
|
||||
"__verge_log__".into(),
|
||||
2,
|
||||
NativeFunction::from_closure(
|
||||
move |_: &JsValue, args: &[JsValue], context: &mut Context| {
|
||||
let level = args.get(0).unwrap().to_string(context)?;
|
||||
let level = level.to_std_string().unwrap();
|
||||
let data = args.get(1).unwrap().to_string(context)?;
|
||||
let data = data.to_std_string().unwrap();
|
||||
let mut out = copy_outputs.lock().unwrap();
|
||||
out.push((level, data));
|
||||
Ok(JsValue::undefined())
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
let _ = context.eval(Source::from_bytes(
|
||||
r#"var console = Object.freeze({
|
||||
log(data){__verge_log__("log",JSON.stringify(data))},
|
||||
info(data){__verge_log__("info",JSON.stringify(data))},
|
||||
error(data){__verge_log__("error",JSON.stringify(data))},
|
||||
debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||
});"#,
|
||||
)?;
|
||||
));
|
||||
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
{script};
|
||||
JSON.stringify(main({config_str})||'')
|
||||
}} catch(err) {{
|
||||
`__error_flag__ ${{err.toString()}}`
|
||||
}}"#
|
||||
);
|
||||
let result: String = ctx.eval(code.as_str())?;
|
||||
);
|
||||
if let Ok(result) = context.eval(Source::from_bytes(code.as_str())) {
|
||||
if !result.is_string() {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
let result = result.to_string(&mut context).unwrap();
|
||||
let result = result.to_std_string().unwrap();
|
||||
if result.starts_with("__error_flag__") {
|
||||
anyhow::bail!(result[15..].to_owned());
|
||||
}
|
||||
if result == "\"\"" {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
});
|
||||
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match result {
|
||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
Err(err) => {
|
||||
out.push(("exception".into(), err.to_string()));
|
||||
Ok((config, out.to_vec()))
|
||||
let res: Result<Mapping, Error> = Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match res {
|
||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
Err(err) => {
|
||||
out.push(("exception".into(), err.to_string()));
|
||||
Ok((config, out.to_vec()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,18 +30,64 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
if enable {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::utils::dirs;
|
||||
use tauri::api::process::Command;
|
||||
log::info!(target: "app", "try to set system dns");
|
||||
let resource_dir = dirs::app_resources_dir().unwrap();
|
||||
let script = resource_dir.join("set_dns.sh");
|
||||
let script = script.to_string_lossy();
|
||||
match Command::new("bash")
|
||||
.args([script])
|
||||
.current_dir(resource_dir)
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
log::info!(target: "app", "set system dns successfully");
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
log::error!(target: "app", "set system dns failed: {code}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "set system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
use_dns_for_tun(config)
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::utils::dirs;
|
||||
use tauri::api::process::Command;
|
||||
log::info!(target: "app", "try to unset system dns");
|
||||
let resource_dir = dirs::app_resources_dir().unwrap();
|
||||
let script = resource_dir.join("unset_dns.sh");
|
||||
let script = script.to_string_lossy();
|
||||
match Command::new("bash")
|
||||
.args([script])
|
||||
.current_dir(resource_dir)
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
log::info!(target: "app", "unset system dns successfully");
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
log::error!(target: "app", "unset system dns failed: {code}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "unset system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,19 @@ use crate::log_err;
|
||||
use crate::utils::resolve;
|
||||
use anyhow::{bail, Result};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use tauri::{AppHandle, ClipboardManager};
|
||||
use tauri::{AppHandle, ClipboardManager, Manager};
|
||||
|
||||
// 打开面板
|
||||
pub fn open_dashboard() {
|
||||
pub fn open_or_close_dashboard() {
|
||||
let handle = handle::Handle::global();
|
||||
let app_handle = handle.app_handle.lock();
|
||||
if let Some(app_handle) = app_handle.as_ref() {
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
if let Ok(true) = window.is_focused() {
|
||||
let _ = window.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve::create_window(app_handle);
|
||||
}
|
||||
}
|
||||
@@ -78,36 +84,6 @@ pub fn toggle_system_proxy() {
|
||||
});
|
||||
}
|
||||
|
||||
// 打开系统代理
|
||||
pub fn enable_system_proxy() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(true),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭系统代理
|
||||
pub fn disable_system_proxy() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_system_proxy: Some(false),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换tun模式
|
||||
pub fn toggle_tun_mode() {
|
||||
let enable = Config::verge().data().enable_tun_mode;
|
||||
@@ -126,41 +102,13 @@ pub fn toggle_tun_mode() {
|
||||
});
|
||||
}
|
||||
|
||||
// 打开tun模式
|
||||
pub fn enable_tun_mode() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(true),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭tun模式
|
||||
pub fn disable_tun_mode() {
|
||||
tauri::async_runtime::spawn(async {
|
||||
match patch_verge(IVerge {
|
||||
enable_tun_mode: Some(false),
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => handle::Handle::refresh_verge(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 修改clash的订阅
|
||||
pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
Config::clash().draft().patch_config(patch.clone());
|
||||
|
||||
match {
|
||||
let redir_port = patch.get("redir-port");
|
||||
let tproxy_port = patch.get("tproxy-port");
|
||||
let mixed_port = patch.get("mixed-port");
|
||||
let socks_port = patch.get("socks-port");
|
||||
let port = patch.get("port");
|
||||
@@ -183,7 +131,9 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
};
|
||||
|
||||
// 激活订阅
|
||||
if mixed_port.is_some()
|
||||
if redir_port.is_some()
|
||||
|| tproxy_port.is_some()
|
||||
|| mixed_port.is_some()
|
||||
|| socks_port.is_some()
|
||||
|| port.is_some()
|
||||
|| patch.get("secret").is_some()
|
||||
@@ -227,34 +177,56 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
let tun_mode = patch.enable_tun_mode;
|
||||
let auto_launch = patch.enable_auto_launch;
|
||||
let system_proxy = patch.enable_system_proxy;
|
||||
let pac = patch.proxy_auto_config;
|
||||
let pac_content = patch.pac_file_content;
|
||||
let proxy_bypass = patch.system_proxy_bypass;
|
||||
let language = patch.language;
|
||||
let port = patch.verge_mixed_port;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let tray_icon = patch.tray_icon;
|
||||
let common_tray_icon = patch.common_tray_icon;
|
||||
let sysproxy_tray_icon = patch.sysproxy_tray_icon;
|
||||
let tun_tray_icon = patch.tun_tray_icon;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let redir_enabled = patch.verge_redir_enabled;
|
||||
#[cfg(target_os = "linux")]
|
||||
let tproxy_enabled = patch.verge_tproxy_enabled;
|
||||
let socks_enabled = patch.verge_socks_enabled;
|
||||
let http_enabled = patch.verge_http_enabled;
|
||||
match {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let service_mode = patch.enable_service_mode;
|
||||
let service_mode = patch.enable_service_mode;
|
||||
|
||||
if service_mode.is_some() {
|
||||
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
|
||||
if service_mode.is_some() {
|
||||
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
|
||||
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
} else if tun_mode.is_some() {
|
||||
update_core_config().await?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if tun_mode.is_some() {
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
} else if tun_mode.is_some() {
|
||||
update_core_config().await?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if redir_enabled.is_some() {
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
if tproxy_enabled.is_some() {
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
}
|
||||
if socks_enabled.is_some() || http_enabled.is_some() {
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
}
|
||||
if auto_launch.is_some() {
|
||||
sysopt::Sysopt::global().update_launch()?;
|
||||
}
|
||||
if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() {
|
||||
if system_proxy.is_some()
|
||||
|| proxy_bypass.is_some()
|
||||
|| port.is_some()
|
||||
|| pac.is_some()
|
||||
|| pac_content.is_some()
|
||||
{
|
||||
sysopt::Sysopt::global().update_sysproxy()?;
|
||||
sysopt::Sysopt::global().guard_proxy();
|
||||
}
|
||||
@@ -269,7 +241,16 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
|
||||
if language.is_some() {
|
||||
handle::Handle::update_systray()?;
|
||||
} else if system_proxy.or(tun_mode).is_some() {
|
||||
} else if system_proxy.is_some()
|
||||
|| tun_mode.is_some()
|
||||
|| common_tray_icon.is_some()
|
||||
|| sysproxy_tray_icon.is_some()
|
||||
|| tun_tray_icon.is_some()
|
||||
{
|
||||
handle::Handle::update_systray_part()?;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
if tray_icon.is_some() {
|
||||
handle::Handle::update_systray_part()?;
|
||||
}
|
||||
|
||||
@@ -349,7 +330,7 @@ pub fn copy_clash_env(app_handle: &AppHandle) {
|
||||
|
||||
let sh =
|
||||
format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
|
||||
let cmd: String = format!("set http_proxy={http_proxy} \n set https_proxy={http_proxy}");
|
||||
let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
|
||||
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
|
||||
|
||||
let mut cliboard = app_handle.clipboard_manager();
|
||||
|
||||
@@ -20,6 +20,9 @@ fn main() -> std::io::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
|
||||
crate::log_err!(init::init_config());
|
||||
|
||||
#[allow(unused_mut)]
|
||||
@@ -33,6 +36,7 @@ fn main() -> std::io::Result<()> {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// common
|
||||
cmds::get_sys_proxy,
|
||||
cmds::get_auto_proxy,
|
||||
cmds::open_app_dir,
|
||||
cmds::open_logs_dir,
|
||||
cmds::open_web_url,
|
||||
@@ -55,6 +59,10 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::get_verge_config,
|
||||
cmds::patch_verge_config,
|
||||
cmds::test_delay,
|
||||
cmds::get_app_dir,
|
||||
cmds::copy_icon_file,
|
||||
cmds::download_icon_cache,
|
||||
cmds::open_devtools,
|
||||
cmds::exit_app,
|
||||
// cmds::update_hotkeys,
|
||||
// profile
|
||||
|
||||
@@ -92,17 +92,16 @@ pub fn clash_pid_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("clash.pid"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn service_dir() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("service"))
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn service_path() -> Result<PathBuf> {
|
||||
Ok(app_resources_dir()?.join("clash-verge-service"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
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)]
|
||||
pub fn service_log_file() -> Result<PathBuf> {
|
||||
use chrono::Local;
|
||||
|
||||
|
||||
@@ -135,6 +135,12 @@ pub fn delete_log() -> Result<()> {
|
||||
for file in fs::read_dir(&log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
|
||||
let service_log_dir = log_dir.join("service");
|
||||
for file in fs::read_dir(&service_log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -240,67 +246,6 @@ pub fn init_resources() -> Result<()> {
|
||||
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
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn init_scheme() -> Result<()> {
|
||||
@@ -359,7 +304,7 @@ pub fn startup_script() -> Result<()> {
|
||||
shell = "powershell";
|
||||
}
|
||||
if path.ends_with(".bat") {
|
||||
shell = "cmd";
|
||||
shell = "powershell";
|
||||
}
|
||||
if shell.is_empty() {
|
||||
return Err(anyhow::anyhow!("unsupported script: {path}"));
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod init;
|
||||
pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod tmpl;
|
||||
pub mod unix_helper;
|
||||
|
||||
@@ -12,6 +12,7 @@ use serde_yaml::Mapping;
|
||||
use std::net::TcpListener;
|
||||
use tauri::api::notification;
|
||||
use tauri::{App, AppHandle, Manager};
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
pub static VERSION: OnceCell<String> = OnceCell::new();
|
||||
|
||||
@@ -41,8 +42,6 @@ pub fn resolve_setup(app: &mut App) {
|
||||
VERSION.get_or_init(|| version.clone());
|
||||
|
||||
log_err!(init::init_resources());
|
||||
#[cfg(target_os = "windows")]
|
||||
log_err!(init::init_service());
|
||||
log_err!(init::init_scheme());
|
||||
log_err!(init::startup_script());
|
||||
// 处理随机端口
|
||||
@@ -156,61 +155,59 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
match builder
|
||||
.decorations(false)
|
||||
.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}"),
|
||||
}
|
||||
}
|
||||
|
||||
let window = builder
|
||||
.decorations(false)
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||
.transparent(true)
|
||||
.visible(false)
|
||||
.build();
|
||||
#[cfg(target_os = "macos")]
|
||||
crate::log_err!(builder
|
||||
let window = builder
|
||||
.decorations(true)
|
||||
.hidden_title(true)
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build());
|
||||
|
||||
.build();
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::log_err!(builder.decorations(true).transparent(false).build());
|
||||
let window = builder.decorations(false).transparent(true).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");
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
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
|
||||
@@ -231,11 +228,11 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
|
||||
let size = size.to_logical::<f64>(scale);
|
||||
let pos = win.outer_position()?;
|
||||
let pos = pos.to_logical::<f64>(scale);
|
||||
|
||||
if size.width >= 600.0 && size.height >= 520.0 {
|
||||
let is_maximized = win.is_maximized()?;
|
||||
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]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -247,6 +244,7 @@ pub async fn resolve_scheme(param: String) {
|
||||
user_agent: None,
|
||||
with_proxy: Some(true),
|
||||
self_proxy: None,
|
||||
danger_accept_invalid_certs: None,
|
||||
update_interval: None,
|
||||
};
|
||||
if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extern crate warp;
|
||||
|
||||
use super::resolve;
|
||||
use crate::config::IVerge;
|
||||
use crate::config::{Config, IVerge, DEFAULT_PAC};
|
||||
use anyhow::{bail, Result};
|
||||
use port_scanner::local_port_available;
|
||||
use std::convert::Infallible;
|
||||
@@ -64,6 +64,22 @@ pub fn embed_server(app_handle: AppHandle) {
|
||||
"ok"
|
||||
});
|
||||
|
||||
let pac = warp::path!("commands" / "pac").map(move || {
|
||||
let content = Config::verge()
|
||||
.latest()
|
||||
.pac_file_content
|
||||
.clone()
|
||||
.unwrap_or(DEFAULT_PAC.to_string());
|
||||
let port = Config::verge()
|
||||
.latest()
|
||||
.verge_mixed_port
|
||||
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||
let content = content.replace("%mixed-port%", &format!("{}", port));
|
||||
warp::http::Response::builder()
|
||||
.header("Content-Type", "application/x-ns-proxy-autoconfig")
|
||||
.body(content)
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let scheme = warp::path!("commands" / "scheme")
|
||||
.and(warp::query::<QueryParam>())
|
||||
.and_then(scheme_handler);
|
||||
@@ -72,7 +88,7 @@ pub fn embed_server(app_handle: AppHandle) {
|
||||
resolve::resolve_scheme(query.param).await;
|
||||
Ok("ok")
|
||||
}
|
||||
let commands = ping.or(visible).or(scheme);
|
||||
let commands = ping.or(visible).or(pac).or(scheme);
|
||||
warp::serve(commands).run(([127, 0, 0, 1], port)).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
//! Some config file template
|
||||
|
||||
/// template for new a profile item
|
||||
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge
|
||||
pub const ITEM_LOCAL: &str = "# Profile Template for Clash Verge
|
||||
|
||||
proxies:
|
||||
proxies: []
|
||||
|
||||
proxy-groups:
|
||||
proxy-groups: []
|
||||
|
||||
rules:
|
||||
rules: []
|
||||
";
|
||||
|
||||
/// enhanced profile
|
||||
pub const ITEM_MERGE: &str = "# Merge Template for clash verge
|
||||
# The `Merge` format used to enhance profile
|
||||
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge
|
||||
|
||||
prepend-rules:
|
||||
prepend-rules: []
|
||||
|
||||
prepend-proxies:
|
||||
prepend-proxies: []
|
||||
|
||||
prepend-proxy-groups:
|
||||
prepend-proxy-groups: []
|
||||
|
||||
append-rules:
|
||||
append-rules: []
|
||||
|
||||
append-proxies:
|
||||
append-proxies: []
|
||||
|
||||
append-proxy-groups:
|
||||
append-proxy-groups: []
|
||||
";
|
||||
|
||||
/// enhanced profile
|
||||
pub const ITEM_SCRIPT: &str = "// Define the `main` function
|
||||
pub const ITEM_SCRIPT: &str = "// Define main function (script entry)
|
||||
|
||||
function main(params) {
|
||||
return params;
|
||||
function main(config) {
|
||||
return config;
|
||||
}
|
||||
";
|
||||
|
||||
14
src-tauri/src/utils/unix_helper.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn linux_elevator() -> &'static str {
|
||||
use std::process::Command;
|
||||
match Command::new("which").arg("pkexec").output() {
|
||||
Ok(output) => {
|
||||
if output.stdout.is_empty() {
|
||||
"sudo"
|
||||
} else {
|
||||
"pkexec"
|
||||
}
|
||||
}
|
||||
Err(_) => "sudo",
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"package": {
|
||||
"productName": "Clash Verge",
|
||||
"version": "1.5.0"
|
||||
"version": "1.6.5"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -17,7 +18,7 @@
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon-new.icns",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": ["resources"],
|
||||
@@ -58,11 +59,23 @@
|
||||
"dialog": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"protocol": {
|
||||
"asset": true,
|
||||
"assetScope": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"fs": {
|
||||
"exists": true,
|
||||
"readFile": true,
|
||||
"scope": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
||||
}
|
||||
},
|
||||
"windows": [],
|
||||
"security": {
|
||||
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src http: https: data: 'self';"
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self';"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.png"
|
||||
"iconPath": "icons/tray-icon.ico"
|
||||
},
|
||||
"bundle": {
|
||||
"targets": ["deb", "appimage", "updater"],
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["deb", "rpm", "appimage", "updater"],
|
||||
"deb": {
|
||||
"depends": ["openssl"],
|
||||
"desktopTemplate": "./template/clash-verge.desktop"
|
||||
"desktopTemplate": "./template/clash-verge.desktop",
|
||||
"provides": ["clash-verge", "clash-meta"],
|
||||
"conflicts": ["clash-verge", "clash-meta"],
|
||||
"replaces": ["clash-verge", "clash-meta"]
|
||||
},
|
||||
"rpm": {
|
||||
"depends": ["openssl"],
|
||||
"desktopTemplate": "./template/clash-verge.desktop",
|
||||
"provides": ["clash-verge", "clash-meta"],
|
||||
"conflicts": ["clash-verge", "clash-meta"],
|
||||
"obsoletes": ["clash-verge", "clash-meta"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/mac-tray-icon.png",
|
||||
"iconPath": "icons/tray-icon-mono.ico",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["app", "dmg", "updater"],
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "10.15",
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": null,
|
||||
"signingIdentity": "-",
|
||||
"entitlements": null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.png"
|
||||
"iconPath": "icons/tray-icon.ico"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["nsis", "updater"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
|
||||
@@ -13,8 +13,10 @@ Unicode true
|
||||
!include FileFunc.nsh
|
||||
!include x64.nsh
|
||||
!include WordFunc.nsh
|
||||
!include "LogicLib.nsh"
|
||||
!include "StrFunc.nsh"
|
||||
!include "Win\COM.nsh"
|
||||
!include "Win\Propkey.nsh"
|
||||
!addplugindir "$%AppData%\Local\NSIS\"
|
||||
${StrCase}
|
||||
${StrLoc}
|
||||
|
||||
@@ -148,7 +150,6 @@ Function PageReinstall
|
||||
; 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
|
||||
@@ -419,56 +420,119 @@ Function .onInit
|
||||
FunctionEnd
|
||||
|
||||
!macro CheckAllVergeProcesses
|
||||
; Check if Clash Verge.exe is running
|
||||
nsis_tauri_utils::FindProcess "Clash Verge.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
!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
|
||||
; Check if clash-verge-service.exe is running
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::FindProcessCurrentUser "clash-verge-service.exe"
|
||||
!else
|
||||
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
!endif
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
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
|
||||
; Check if clash-meta-alpha.exe is running
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::FindProcessCurrentUser "clash-meta-alpha.exe"
|
||||
!else
|
||||
nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
!endif
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
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
|
||||
; Check if clash-meta.exe is running
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::FindProcessCurrentUser "clash-meta.exe"
|
||||
!else
|
||||
nsis_tauri_utils::FindProcess "clash-meta.exe"
|
||||
${If} $R0 != 0
|
||||
; Kill the process
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "clash-meta.exe"
|
||||
!endif
|
||||
${EndIf}
|
||||
!endif
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
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
|
||||
|
||||
Section
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
SectionEnd
|
||||
!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
|
||||
@@ -505,7 +569,7 @@ Section WebView2
|
||||
!if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
|
||||
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
DetailPrint "$(webview2Downloading)"
|
||||
nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
NSISdl::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
|
||||
Pop $0
|
||||
${If} $0 == 0
|
||||
DetailPrint "$(webview2DownloadSuccess)"
|
||||
@@ -608,6 +672,8 @@ Section Install
|
||||
File /a "/oname={{this}}" "{{@key}}"
|
||||
{{/each}}
|
||||
|
||||
!insertmacro StartVergeService
|
||||
|
||||
; Create uninstaller
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
@@ -662,7 +728,8 @@ Function .onInstSuccess
|
||||
check_r_flag:
|
||||
${GetOptions} $CMDLINE "/R" $R0
|
||||
IfErrors run_done 0
|
||||
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
|
||||
${GetOptions} $CMDLINE "/ARGS" $R0
|
||||
Exec '"$INSTDIR\${MAINBINARYNAME}.exe" $R0'
|
||||
run_done:
|
||||
FunctionEnd
|
||||
|
||||
@@ -676,9 +743,39 @@ Function un.onInit
|
||||
!insertmacro MUI_UNGETLANGUAGE
|
||||
FunctionEnd
|
||||
|
||||
!macro DeleteAppUserModelId
|
||||
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_DestinationList} ${IID_ICustomDestinationList} r1 ""
|
||||
${If} $1 P<> 0
|
||||
${ICustomDestinationList::DeleteList} $1 '("${BUNDLEID}")'
|
||||
${IUnknown::Release} $1 ""
|
||||
${EndIf}
|
||||
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ApplicationDestinations} ${IID_IApplicationDestinations} r1 ""
|
||||
${If} $1 P<> 0
|
||||
${IApplicationDestinations::SetAppID} $1 '("${BUNDLEID}")i.r0'
|
||||
${If} $0 >= 0
|
||||
${IApplicationDestinations::RemoveAllDestinations} $1 ''
|
||||
${EndIf}
|
||||
${IUnknown::Release} $1 ""
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
; From https://stackoverflow.com/a/42816728/16993372
|
||||
!macro UnpinShortcut shortcut
|
||||
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_StartMenuPin} ${IID_IStartMenuPinnedList} r0 ""
|
||||
${If} $0 P<> 0
|
||||
System::Call 'SHELL32::SHCreateItemFromParsingName(ws, p0, g "${IID_IShellItem}", *p0r1)' "${shortcut}"
|
||||
${If} $1 P<> 0
|
||||
${IStartMenuPinnedList::RemoveFromList} $0 '(r1)'
|
||||
${IUnknown::Release} $1 ""
|
||||
${EndIf}
|
||||
${IUnknown::Release} $0 ""
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Section Uninstall
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
!insertmacro RemoveVergeService
|
||||
; Delete the app directory and its content from disk
|
||||
; Copy main executable
|
||||
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
@@ -687,7 +784,7 @@ Section Uninstall
|
||||
{{#each resources}}
|
||||
Delete "$INSTDIR\\{{this.[1]}}"
|
||||
{{/each}}
|
||||
|
||||
Delete "$INSTDIR\resources"
|
||||
; Delete external binaries
|
||||
{{#each binaries}}
|
||||
Delete "$INSTDIR\\{{this}}"
|
||||
@@ -696,14 +793,14 @@ Section Uninstall
|
||||
; 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}
|
||||
{{#each resources_ancestors}}
|
||||
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
RMDir "$INSTDIR"
|
||||
|
||||
!insertmacro DeleteAppUserModelId
|
||||
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
|
||||
!insertmacro UnpinShortcut "$DESKTOP\${MAINBINARYNAME}.lnk"
|
||||
|
||||
; Remove start menu shortcut
|
||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
||||
@@ -746,13 +843,39 @@ Function SkipIfPassive
|
||||
${IfThen} $PassiveMode == 1 ${|} Abort ${|}
|
||||
FunctionEnd
|
||||
|
||||
!macro SetLnkAppUserModelId shortcut
|
||||
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 ""
|
||||
${If} $0 P<> 0
|
||||
${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}",.r1)'
|
||||
${If} $1 P<> 0
|
||||
${IPersistFile::Load} $1 '("${shortcut}", ${STGM_READWRITE})'
|
||||
${IUnknown::QueryInterface} $0 '("${IID_IPropertyStore}",.r2)'
|
||||
${If} $2 P<> 0
|
||||
System::Call 'Oleaut32::SysAllocString(w "${BUNDLEID}") i.r3'
|
||||
System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})p.r4'
|
||||
System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_BSTR},,&i4 $3)p.r5'
|
||||
${IPropertyStore::SetValue} $2 '($4,$5)'
|
||||
|
||||
System::Call 'Oleaut32::SysFreeString($3)'
|
||||
System::Free $4
|
||||
System::Free $5
|
||||
${IPropertyStore::Commit} $2 ""
|
||||
${IUnknown::Release} $2 ""
|
||||
${IPersistFile::Save} $1 '("${shortcut}",1)'
|
||||
${EndIf}
|
||||
${IUnknown::Release} $1 ""
|
||||
${EndIf}
|
||||
${IUnknown::Release} $0 ""
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Function CreateDesktopShortcut
|
||||
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
|
||||
!insertmacro SetLnkAppUserModelId "$DESKTOP\${MAINBINARYNAME}.lnk"
|
||||
FunctionEnd
|
||||
|
||||
Function CreateStartMenuShortcut
|
||||
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
|
||||
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
|
||||
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
|
||||
FunctionEnd
|
||||
|
||||
38
src-tauri/webview2.arm64.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.ico"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["nsis", "updater"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "",
|
||||
"webviewInstallMode": {
|
||||
"type": "fixedRuntime",
|
||||
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.arm64/"
|
||||
},
|
||||
"nsis": {
|
||||
"displayLanguageSelector": true,
|
||||
"installerIcon": "icons/icon.ico",
|
||||
"languages": ["SimpChinese", "English"],
|
||||
"license": "../LICENSE",
|
||||
"installMode": "perMachine",
|
||||
"template": "./template/installer.nsi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"dialog": false,
|
||||
"endpoints": [
|
||||
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
||||
],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src-tauri/webview2.x64.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.ico"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["nsis", "updater"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "",
|
||||
"webviewInstallMode": {
|
||||
"type": "fixedRuntime",
|
||||
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x64/"
|
||||
},
|
||||
"nsis": {
|
||||
"displayLanguageSelector": true,
|
||||
"installerIcon": "icons/icon.ico",
|
||||
"languages": ["SimpChinese", "English"],
|
||||
"license": "../LICENSE",
|
||||
"installMode": "perMachine",
|
||||
"template": "./template/installer.nsi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"dialog": false,
|
||||
"endpoints": [
|
||||
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
||||
],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src-tauri/webview2.x86.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.ico"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["nsis", "updater"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "",
|
||||
"webviewInstallMode": {
|
||||
"type": "fixedRuntime",
|
||||
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x86/"
|
||||
},
|
||||
"nsis": {
|
||||
"displayLanguageSelector": true,
|
||||
"installerIcon": "icons/icon.ico",
|
||||
"languages": ["SimpChinese", "English"],
|
||||
"license": "../LICENSE",
|
||||
"installMode": "perMachine",
|
||||
"template": "./template/installer.nsi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"dialog": false,
|
||||
"endpoints": [
|
||||
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
|
||||
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
|
||||
],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/assets/image/component/match_case.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M 175.755 768.851 L 334.113 343.826 L 394.912 343.826 L 554.501 768.851 L 494.851 768.851 L 453.989 653.897 L 275.036 653.897 L 234.583 768.851 L 175.755 768.851 Z M 293.332 604.339 L 436.102 604.339 L 366.441 406.757 L 362.994 406.757 L 293.332 604.339 Z M 703.344 780.174 C 670.415 780.174 644.541 771.518 625.724 754.205 C 606.907 736.892 597.499 713.412 597.499 683.764 C 597.499 653.898 608.575 629.61 630.729 610.902 C 652.882 592.195 681.682 582.842 717.129 582.842 C 732.663 582.842 747.433 584.223 761.437 586.985 C 775.44 589.747 787.419 593.973 797.375 599.662 L 797.375 580.626 C 797.375 557.323 790.66 539.299 777.231 526.554 C 763.802 513.808 744.753 507.435 720.082 507.435 C 705.423 507.435 691.652 510.293 678.769 516.01 C 665.887 521.726 654.004 530.218 643.118 541.486 L 607.099 512.687 C 621.43 495.948 637.95 483.422 656.657 475.107 C 675.365 466.793 696.726 462.636 720.739 462.636 C 762.639 462.636 794.365 472.988 815.918 493.693 C 837.469 514.397 848.245 544.797 848.245 584.893 L 848.245 770.574 L 798.031 770.574 L 798.031 731.025 L 794.421 731.025 C 784.903 746.889 772.39 759.046 756.882 767.498 C 741.375 775.948 723.529 780.174 703.344 780.174 Z M 709.087 736.688 C 734.031 736.688 754.981 727.594 771.939 709.406 C 788.896 691.218 797.375 668.694 797.375 641.836 C 787.693 636.147 776.315 631.812 763.242 628.831 C 750.168 625.849 737.751 624.358 725.99 624.358 C 701.594 624.358 682.735 629.555 669.415 639.949 C 656.096 650.342 649.436 664.947 649.436 683.764 C 649.436 699.628 654.906 712.414 665.846 722.124 C 676.786 731.833 691.2 736.688 709.087 736.688 Z"
|
||||
p-id="11663" transform="matrix(1, 0, 0, 1, 0, 3.552713678800501e-15)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
6
src/assets/image/component/match_whole_word.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M 64.002 831.066 L 64.002 649.735 L 128 649.735 L 128 767.067 L 896 767.067 L 896 649.735 L 959.999 649.735 L 959.999 831.066 L 64.001 831.066 L 64.002 831.066 Z M 414.031 680.257 L 414.031 640.708 L 410.421 640.708 C 400.903 656.571 388.39 668.729 372.882 677.18 C 357.375 685.631 339.529 689.857 319.344 689.857 C 286.688 689.857 260.883 681.2 241.929 663.887 C 222.975 646.575 213.499 623.095 213.499 593.447 C 213.499 563.58 224.575 539.293 246.729 520.585 C 268.883 501.878 297.546 492.524 332.719 492.524 C 348.254 492.524 363.023 493.905 377.026 496.667 C 391.029 499.43 403.146 503.656 413.375 509.345 L 413.375 490.309 C 413.375 467.006 406.729 448.982 393.437 436.236 C 380.144 423.49 361.299 417.117 336.902 417.117 C 321.969 417.117 307.993 419.976 294.975 425.692 C 281.956 431.408 270.004 439.9 259.118 451.169 L 223.099 422.37 C 237.43 405.631 253.95 393.104 272.657 384.79 C 291.365 376.476 312.862 372.318 337.149 372.318 C 379.05 372.318 410.708 382.671 432.123 403.375 C 453.538 424.079 464.245 454.479 464.245 494.575 L 464.245 680.257 L 414.031 680.257 Z M 341.99 534.041 C 317.867 534.041 299.077 539.238 285.62 549.631 C 272.164 560.024 265.436 574.493 265.436 593.036 C 265.436 609.174 270.974 622.097 282.051 631.806 C 293.128 641.516 307.61 646.371 325.498 646.371 C 350.441 646.371 371.323 637.277 388.144 619.089 C 404.965 600.9 413.375 578.377 413.375 551.518 C 403.693 545.83 392.315 541.495 379.242 538.514 C 366.168 535.532 353.751 534.041 341.99 534.041 Z M 541.375 680.667 L 541.375 252.934 L 593.558 252.934 L 593.558 384.545 L 590.358 427.211 L 593.148 427.211 C 598.564 418.733 608.943 408.435 624.287 396.319 C 639.631 384.203 660.977 378.145 688.327 378.145 C 729.735 378.145 762.638 393.27 787.035 423.519 C 811.431 453.769 823.629 491.047 823.629 535.355 C 823.629 579.116 811.609 615.874 787.568 645.631 C 763.527 675.389 730.31 690.267 687.917 690.267 C 662.044 690.267 641.258 684.688 625.558 673.529 C 609.859 662.37 599.056 651.594 593.148 641.201 L 590.358 641.201 L 590.358 680.667 L 541.375 680.667 Z M 681.6 426.801 C 653.702 426.801 631.521 437.563 615.056 459.087 C 598.591 480.613 590.358 505.816 590.358 534.698 C 590.358 564.018 598.591 589.263 615.056 610.433 C 631.521 631.602 653.702 642.186 681.6 642.186 C 709.497 642.186 731.309 631.807 747.036 611.048 C 762.762 590.289 770.625 564.839 770.625 534.698 C 770.625 504.558 762.762 479.04 747.036 458.145 C 731.309 437.249 709.497 426.801 681.6 426.801 Z"
|
||||
p-id="12920" transform="matrix(1, 0, 0, 1, 0, 1.4210854715202004e-14)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
9
src/assets/image/component/use_regular_expression.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M 838.757 427.686 L 808.603 338.337 L 633.84 409.02 L 645.314 217.721 L 549.541 217.721 L 561.015 409.02 L 386.252 338.337 L 356.098 427.686 L 539.205 477.619 L 413.277 620.027 L 487.146 678.203 L 597.428 519.119 L 706.666 678.203 L 780.535 620.027 L 654.607 477.619 L 838.757 427.686 Z"
|
||||
p-id="15003" style="transform-origin: 597.428px 447.962px;" />
|
||||
<path
|
||||
d="M 185.244 735.674 C 185.244 789.945 243.994 823.864 290.994 796.729 C 312.807 784.135 326.244 760.861 326.244 735.674 C 326.244 681.403 267.494 647.484 220.494 674.619 C 198.681 687.213 185.244 710.487 185.244 735.674 Z"
|
||||
p-id="15004" style="transform-origin: 255.744px 735.674px;" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 803 B |
5
src/assets/image/icon_dark.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 512.00 512.00"
|
||||
width="512.00" height="512.00">
|
||||
<path fill="#ffffff"
|
||||
d=" M 45.65 297.77 C 50.31 280.20 56.48 263.74 64.10 247.67 C 66.07 243.51 65.99 238.92 65.73 234.44 Q 63.32 191.66 64.13 148.11 C 64.39 134.30 66.21 57.13 87.19 55.68 Q 94.10 55.20 99.69 57.92 Q 109.79 62.83 121.61 72.92 C 143.03 91.20 162.36 112.69 183.36 130.92 C 186.54 133.67 193.35 138.99 197.10 138.76 Q 198.57 138.67 200.07 138.26 Q 222.12 132.30 238.17 131.29 C 256.93 130.11 277.29 130.43 296.13 134.49 Q 305.05 136.41 313.24 138.56 C 318.37 139.92 325.54 133.72 329.51 130.33 Q 339.55 121.75 347.99 113.37 C 364.54 96.94 380.60 80.49 399.04 66.35 C 403.86 62.66 409.75 58.98 415.36 56.95 C 419.03 55.63 425.97 54.84 429.34 57.30 C 433.77 60.52 436.73 66.64 438.49 71.86 Q 441.73 81.45 443.26 90.82 Q 447.60 117.52 448.00 151.51 Q 448.45 189.74 447.59 207.00 Q 446.70 225.12 446.21 240.03 C 446.12 242.85 446.89 245.55 448.09 248.07 Q 459.73 272.71 466.27 297.70 C 467.59 302.75 468.45 308.08 467.82 313.31 C 466.21 326.87 459.76 339.57 452.24 350.80 Q 436.72 374.00 411.37 395.51 Q 374.63 426.67 330.92 443.23 Q 272.04 465.54 211.41 452.34 Q 188.54 447.36 165.13 436.61 Q 121.44 416.54 86.71 382.78 Q 69.63 366.18 57.73 347.55 C 50.80 336.69 44.86 323.90 44.03 311.09 Q 43.65 305.29 45.65 297.77 Z M 131.34 313.94 C 140.29 332.22 157.72 341.20 177.30 342.68 Q 184.65 343.24 193.22 340.65 Q 202.03 338.00 205.56 330.26 C 211.13 318.09 200.76 303.01 191.81 296.02 C 179.37 286.31 161.98 280.10 146.19 280.97 Q 137.21 281.47 131.35 287.14 C 124.01 294.24 127.17 305.43 131.34 313.94 Z M 349.22 281.81 C 332.78 284.95 316.93 292.71 307.08 305.92 C 303.14 311.22 300.42 317.96 301.07 324.43 C 302.18 335.36 310.18 340.08 320.43 341.92 C 336.31 344.78 355.06 339.00 366.59 328.03 C 376.14 318.95 389.80 294.29 373.19 284.22 C 366.55 280.20 356.95 280.33 349.22 281.81 Z M 226.25 381.62 C 232.99 389.35 240.71 395.69 249.97 398.50 C 259.93 401.51 272.87 391.21 279.39 384.18 C 281.43 381.98 283.70 379.66 284.61 376.72 C 285.41 374.13 282.30 371.54 280.28 370.59 Q 276.07 368.62 271.56 368.03 Q 254.57 365.79 237.08 367.97 Q 232.61 368.53 228.23 370.40 C 225.86 371.41 222.31 374.22 223.50 377.19 Q 224.45 379.55 226.25 381.62 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
5
src/assets/image/icon_light.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 512.00 512.00"
|
||||
width="512.00" height="512.00">
|
||||
<path fill="#000000"
|
||||
d=" M 66.75 243.26 C 64.36 202.61 63.47 160.98 66.14 119.90 Q 67.07 105.54 69.56 90.86 C 71.35 80.37 74.26 67.20 81.13 59.68 C 88.86 51.20 102.34 59.42 109.45 64.46 Q 122.61 73.79 137.56 88.26 Q 154.16 104.32 170.37 120.15 Q 177.39 127.01 185.78 133.69 C 189.58 136.71 194.75 140.48 199.81 139.03 Q 256.12 122.89 312.63 139.17 C 317.17 140.47 322.43 136.89 326.29 133.81 Q 334.64 127.18 341.86 120.15 Q 358.44 104.02 373.87 89.06 Q 389.67 73.75 403.99 63.72 Q 409.86 59.61 416.68 57.20 C 430.17 52.45 435.71 64.65 438.76 74.78 Q 442.82 88.24 444.57 104.64 Q 447.71 133.95 447.66 168.99 Q 447.61 205.59 445.24 243.61 Q 445.21 244.12 445.44 244.57 Q 459.30 271.43 466.56 302.09 C 469.00 312.41 465.64 324.20 461.06 333.82 C 449.65 357.80 430.14 378.99 409.62 396.13 Q 372.77 426.90 329.61 443.00 Q 266.07 466.70 201.80 449.27 C 162.55 438.62 125.61 417.06 95.28 389.88 C 77.45 373.90 60.60 354.63 50.57 332.92 C 46.30 323.66 43.03 312.16 45.33 302.37 Q 52.57 271.58 66.46 244.63 Q 66.80 243.98 66.75 243.26 Z M 129.31 310.72 Q 136.38 328.58 152.74 336.68 C 165.31 342.91 181.44 345.53 194.60 340.75 C 211.72 334.54 209.96 316.29 200.74 304.29 C 190.53 291.00 173.63 283.30 157.10 280.73 C 136.41 277.52 120.03 287.25 129.31 310.72 Z M 304.10 309.36 C 297.35 321.61 299.56 335.79 313.93 340.88 C 326.42 345.31 342.09 343.01 354.08 337.35 Q 374.66 327.63 380.68 304.95 C 386.50 282.97 365.69 278.03 349.30 281.14 C 331.39 284.54 312.80 293.56 304.10 309.36 Z M 244.39 396.99 Q 252.76 401.23 260.59 398.28 Q 271.64 394.13 281.68 382.89 C 290.72 372.77 280.23 368.82 272.04 367.56 Q 253.06 364.63 234.76 367.80 C 228.71 368.85 218.66 372.23 224.67 380.57 Q 231.98 390.72 244.39 396.99 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
10
src/assets/image/itemicon/connections.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_118)"/>
|
||||
<path d="M17.9917 9.66675C13.3917 9.66675 9.66669 13.4001 9.66669 18.0001C9.66669 22.6001 13.3917 26.3334 17.9917 26.3334C22.6 26.3334 26.3334 22.6001 26.3334 18.0001C26.3334 13.4001 22.6 9.66675 17.9917 9.66675ZM23.7667 14.6667H21.3084C21.0417 13.6251 20.6584 12.6251 20.1584 11.7001C21.6917 12.2251 22.9667 13.2917 23.7667 14.6667ZM18 11.3667C18.6917 12.3667 19.2334 13.4751 19.5917 14.6667H16.4084C16.7667 13.4751 17.3084 12.3667 18 11.3667ZM11.55 19.6667C11.4167 19.1334 11.3334 18.5751 11.3334 18.0001C11.3334 17.4251 11.4167 16.8667 11.55 16.3334H14.3667C14.3 16.8834 14.25 17.4334 14.25 18.0001C14.25 18.5667 14.3 19.1167 14.3667 19.6667H11.55ZM12.2334 21.3334H14.6917C14.9584 22.3751 15.3417 23.3751 15.8417 24.3001C14.3084 23.7751 13.0334 22.7167 12.2334 21.3334ZM14.6917 14.6667H12.2334C13.0334 13.2834 14.3084 12.2251 15.8417 11.7001C15.3417 12.6251 14.9584 13.6251 14.6917 14.6667ZM18 24.6334C17.3084 23.6334 16.7667 22.5251 16.4084 21.3334H19.5917C19.2334 22.5251 18.6917 23.6334 18 24.6334ZM19.95 19.6667H16.05C15.975 19.1167 15.9167 18.5667 15.9167 18.0001C15.9167 17.4334 15.975 16.8751 16.05 16.3334H19.95C20.025 16.8751 20.0834 17.4334 20.0834 18.0001C20.0834 18.5667 20.025 19.1167 19.95 19.6667ZM20.1584 24.3001C20.6584 23.3751 21.0417 22.3751 21.3084 21.3334H23.7667C22.9667 22.7084 21.6917 23.7751 20.1584 24.3001ZM21.6334 19.6667C21.7 19.1167 21.75 18.5667 21.75 18.0001C21.75 17.4334 21.7 16.8834 21.6334 16.3334H24.45C24.5834 16.8667 24.6667 17.4251 24.6667 18.0001C24.6667 18.5751 24.5834 19.1334 24.45 19.6667H21.6334Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_971_118" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#009038"/>
|
||||
<stop offset="1" stop-color="#1CA350"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |