Compare commits

...
42 Commits
Author SHA1 Message Date
wonfen 2cf7a048cf Release 1.5.11 2024-03-28 12:06:41 +08:00
MystiPanda 3dbb71a076 feat: Support Persian
#715
2024-03-21 20:51:33 +08:00
MystiPanda ddf6760543 fix: Duplicate icon display error
#719
2024-03-21 20:29:16 +08:00
MystiPanda a6fa323f33 fix: The update button cannot be clicked
#716
2024-03-21 19:56:30 +08:00
MystiPanda 2df8f1acdb fix: Check if the install directory is empty when uninstall 2024-03-21 15:24:12 +08:00
MystiPanda 74a3290abc chore: fix typo 2024-03-21 14:09:59 +08:00
MystiPanda ab306d21f3 Release 1.5.10 2024-03-21 13:47:39 +08:00
MystiPanda 1bd7795851 chore: Change a little 2024-03-21 12:55:22 +08:00
MystiPanda 302c142346 build: Update depends 2024-03-21 11:53:25 +08:00
MystiPanda 7c254c9b45 fix: a little 2024-03-21 11:43:16 +08:00
MystiPanda b1c458359d feat: Confirm before deletion
#703
2024-03-21 11:39:01 +08:00
MystiPanda 2d925c62f5 feat: Support config redir port and tproxy port 2024-03-21 10:54:56 +08:00
MystiPanda ef4711987f chore: limit port config
#699
2024-03-20 21:23:10 +08:00
MystiPanda 8910dfaecf fix: Limit icon width
#697
2024-03-20 20:38:45 +08:00
MystiPanda e8b2e79c7d fix: service logs are not cleared
#695
2024-03-20 20:31:00 +08:00
MystiPanda 333ee1e1f7 docs: Improve the issue template 2024-03-20 18:06:02 +08:00
MystiPanda ba013bbe96 feat: Optimize Linux tray menu 2024-03-18 11:11:18 +08:00
MystiPanda 636e66fe7a Revert "fix: remove activation policy" 2024-03-18 09:53:48 +08:00
MystiPanda 372a45e667 Release 1.5.9 2024-03-17 11:25:25 +08:00
MystiPanda ab6b796ce2 fix: drag error
#643
2024-03-16 20:37:39 +08:00
MystiPanda 71e6c02897 fix: drag error 2024-03-16 17:06:59 +08:00
MystiPanda 3edeaa7038 fix: Try to fix touch drag
#456
2024-03-16 10:54:19 +08:00
MystiPanda f2f75d4015 build: Update to Tauri 1.6.1 2024-03-16 00:03:34 +08:00
MystiPanda 69adade738 ci: update alpha script 2024-03-15 21:52:56 +08:00
MystiPanda 1bd88828e4 ci: remove 2024-03-15 21:42:55 +08:00
MystiPanda 977fcbe6cd feat: Try to support more architecture 2024-03-15 21:14:05 +08:00
MystiPanda aa76a5c436 refactor: Try to migrate to boa_engine
#634
2024-03-15 19:58:22 +08:00
MystiPanda ea5fad428d fix: Avoid empty user-agent 2024-03-15 18:27:24 +08:00
MystiPanda 0e1e27b35a feat: Try to cache remote images
#603
2024-03-15 16:43:39 +08:00
screw-hand d41f67a156 fix: mac setting theme font-famil not working (#632) 2024-03-15 15:17:05 +08:00
MystiPanda 6f10dec808 fix: remove activation policy
#592
2024-03-15 12:44:25 +08:00
xkww3n 9b79da2cdf fix: Set REJECT-DROP policy the same text color as REJECT (#622) 2024-03-14 18:54:26 +08:00
MystiPanda c11b8519f1 Release 1.5.8 2024-03-13 14:17:06 +08:00
wonfen b9e23a0b59 Sytle: A little tweak 2024-03-13 14:09:08 +08:00
MystiPanda 75d41b6fe5 feat: Add border-radius for window on linux 2024-03-13 13:58:29 +08:00
Amnesiash 4256590735 update profile ui (#594) 2024-03-13 10:53:39 +08:00
MystiPanda 462fb05ea8 fix: Try to fix #577 again 2024-03-11 22:52:29 +08:00
MystiPanda 812dbfd836 fix: script 2024-03-11 22:10:20 +08:00
MystiPanda cfca837777 fix: typo 2024-03-11 22:05:56 +08:00
MystiPanda ac5fb1948a fix: Try to fix #577 2024-03-11 22:03:45 +08:00
MystiPanda 22d8b73625 feat: Allow open devtools 2024-03-11 20:19:21 +08:00
MystiPanda edde40c298 docs: Update Readme 2024-03-11 18:38:58 +08:00
57 changed files with 1916 additions and 612 deletions
+33 -22
View File
@@ -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.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq.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.html) and [FAQ](https://clash-verge-rev.github.io/faq.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
+26 -18
View File
@@ -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.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.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.
+12 -2
View File
@@ -14,7 +14,7 @@ elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
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++
@@ -24,12 +24,22 @@ elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
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
+51 -15
View File
@@ -30,17 +30,6 @@ 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
@@ -57,7 +46,7 @@ jobs:
with:
node-version: "20"
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -100,6 +89,8 @@ jobs:
target: x86_64-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
@@ -112,30 +103,56 @@ jobs:
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
- run: |
cat > release.txt << 'EOF'
### 我应该下载哪个版本?
- Windows x86_64架构: x64-setup.exe (不支持win7)
- Windows arm64架构: arm64-setup.exe
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
- Linux x64架构: amd64.AppImage/amd64.deb
- Linux arm64架构: arm64.deb
- Linux armv7架构: armhf.deb
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
### FAQ
- [https://clash-verge-rev.github.io/faq.html](https://clash-verge-rev.github.io/faq.html)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
if: startsWith(matrix.target, 'x86_64')
uses: softprops/action-gh-release@v1
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
- name: Upload Release
uses: softprops/action-gh-release@v1
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
update_tag:
name: Update tag
runs-on: ubuntu-latest
@@ -143,21 +160,40 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Update Tag
uses: richardsimko/update-tag@v1
with:
tag_name: alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
## Clash Verge Rev Alpha
### 我应该下载哪个版本?
- Windows x86_64架构: x64-setup.exe (不支持win7)
- Windows arm64架构: arm64-setup.exe
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
- Linux x64架构: amd64.AppImage/amd64.deb
- Linux arm64架构: arm64.deb
- Linux armv7架构: armhf.deb
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
### FAQ
- [https://clash-verge-rev.github.io/faq.html](https://clash-verge-rev.github.io/faq.html)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v1
with:
+6 -12
View File
@@ -27,17 +27,6 @@ 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
@@ -54,7 +43,7 @@ jobs:
with:
node-version: "20"
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 8
@@ -97,6 +86,8 @@ jobs:
target: x86_64-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
@@ -109,11 +100,13 @@ jobs:
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
target: ${{ matrix.target }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
- name: Upload Release
if: startsWith(matrix.target, 'x86_64')
uses: softprops/action-gh-release@v1
@@ -123,6 +116,7 @@ jobs:
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
with:
+1
View File
@@ -6,3 +6,4 @@ dist-ssr
update.json
scripts/_env.sh
.vscode
.tool-version
+1
View File
@@ -0,0 +1 @@
nodejs 21.7.1
+4 -4
View File
@@ -9,6 +9,10 @@
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
</h3>
## Preview
![preview](./docs/preview.png)
## Install
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
@@ -43,10 +47,6 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
- System proxy setting and guard.
## Preview
![preview](./docs/preview.gif)
### FAQ
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq.html)
+63
View File
@@ -1,3 +1,66 @@
## 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
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "1.5.7",
"version": "1.5.11",
"license": "GPL-3.0-only",
"scripts": {
"dev": "tauri dev",
-195
View File
@@ -1,195 +0,0 @@
From 871c9a6d1ed014c93da2436a437df03734e9f76c 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 2f1a3be..d67f6ed 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"
once_cell = "1.18"
@@ -33,6 +32,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 30a922f..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
+16
View File
@@ -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",
};
@@ -65,6 +67,7 @@ const META_ALPHA_MAP = {
"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",
};
@@ -111,6 +114,7 @@ const META_MAP = {
"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",
};
@@ -370,6 +374,16 @@ const resolveUninstall = () =>
file: "uninstall-service.exe",
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
});
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({
file: "Country.mmdb",
@@ -409,6 +423,8 @@ const tasks = [
{ 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: "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 },
+1009 -173
View File
File diff suppressed because it is too large Load Diff
+5 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "1.5.7"
version = "1.5.11"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@@ -14,18 +14,16 @@ tauri-build = { version = "1", features = [] }
[dependencies]
warp = "0.3"
which = "6.0.0"
anyhow = "1.0"
dirs = "5.0"
open = "5.0"
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"
once_cell = "1.18"
@@ -36,13 +34,13 @@ 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" }
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
tauri = { version = "1.5", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
tauri = { version = "1.6", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "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"
+29 -1
View File
@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
use serde_yaml::Mapping;
use std::collections::{HashMap, VecDeque};
use sysproxy::Sysproxy;
use tauri::api;
use tauri::{api, Manager};
type CmdResult<T = ()> = Result<T, String>;
#[tauri::command]
@@ -275,6 +275,23 @@ pub fn get_app_dir() -> CmdResult<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);
@@ -294,6 +311,17 @@ pub fn copy_icon_file(path: String, name: String) -> CmdResult<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);
+43 -2
View File
@@ -39,7 +39,10 @@ impl IClashTemp {
tun.insert("auto-detect-interface".into(), true.into());
tun.insert("dns-hijack".into(), vec!["any:53"].into());
tun.insert("mtu".into(), 9000.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());
@@ -54,11 +57,18 @@ impl IClashTemp {
}
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());
@@ -110,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
+15 -1
View File
@@ -126,7 +126,13 @@ pub struct IVerge {
/// 是否启用随机端口
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(target_os = "linux")]
pub verge_tproxy_port: Option<u16>,
pub verge_mixed_port: Option<u16>,
pub verge_socks_port: Option<u16>,
@@ -190,6 +196,10 @@ impl IVerge {
enable_silent_start: Some(false),
enable_system_proxy: Some(false),
enable_random_port: Some(false),
#[cfg(not(target_os = "windows"))]
verge_redir_port: Some(7895),
#[cfg(target_os = "linux")]
verge_tproxy_port: Some(7896),
verge_mixed_port: Some(7897),
verge_socks_port: Some(7898),
verge_port: Some(7899),
@@ -239,6 +249,10 @@ impl IVerge {
patch!(enable_auto_launch);
patch!(enable_silent_start);
patch!(enable_random_port);
#[cfg(not(target_os = "windows"))]
patch!(verge_redir_port);
#[cfg(target_os = "linux")]
patch!(verge_tproxy_port);
patch!(verge_mixed_port);
patch!(verge_socks_port);
patch!(verge_port);
-32
View File
@@ -105,22 +105,6 @@ impl CoreManager {
sleep(Duration::from_millis(500)).await;
}
#[cfg(target_os = "macos")]
{
let enable_tun = Config::verge().latest().enable_tun_mode.clone();
let enable_tun = enable_tun.unwrap_or(false);
log::debug!(target: "app", "try to set system dns");
if enable_tun {
let script = include_str!("./script/set_dns.sh");
match (|| async { Command::new("bash").args([script]).output() })().await {
Ok(_) => return Ok(()),
Err(err) => {
log::error!(target: "app", "{err}");
}
}
}
}
#[cfg(target_os = "windows")]
{
use super::win_service;
@@ -263,22 +247,6 @@ impl CoreManager {
return Ok(());
}
#[cfg(target_os = "macos")]
{
let enable_tun = Config::verge().latest().enable_tun_mode.clone();
let enable_tun = enable_tun.unwrap_or(false);
log::debug!(target: "app", "try to unset system dns");
if enable_tun {
let script = include_str!("./script/unset_dns.sh");
match (|| Command::new("bash").args([script]).output())() {
Ok(_) => return Ok(()),
Err(err) => {
log::error!(target: "app", "{err}");
}
}
}
}
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
log::debug!(target: "app", "stop the core by sidecar");
-5
View File
@@ -1,5 +0,0 @@
nic=$(route -n get default | grep "interface" | awk '{print $2}')
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '/Hardware Port/{port=$3} /Device:/{if ($2 == dev) {print port; exit}}')
networksetup -setdnsservers $hardware_port 223.5.5.5
-5
View File
@@ -1,5 +0,0 @@
nic=$(route -n get default | grep "interface" | awk '{print $2}')
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '/Hardware Port/{port=$3} /Device:/{if ($2 == dev) {print port; exit}}')
networksetup -setdnsservers $hardware_port Empty
+59
View File
@@ -130,6 +130,44 @@ 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);
@@ -182,6 +220,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();
+3 -1
View File
@@ -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",
+44 -31
View File
@@ -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
View File
@@ -34,8 +34,38 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
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]).output() {
Ok(_) => log::info!(target: "app", "set system dns successfully"),
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]).output() {
Ok(_) => log::info!(target: "app", "unset system dns successfully"),
Err(err) => {
log::error!(target: "app", "unset system dns failed: {err}");
}
}
}
config
}
}
+5 -1
View File
@@ -107,6 +107,8 @@ 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");
@@ -129,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()
+2
View File
@@ -57,6 +57,8 @@ fn main() -> std::io::Result<()> {
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
+7
View File
@@ -135,6 +135,13 @@ pub fn delete_log() -> Result<()> {
for file in fs::read_dir(&log_dir)?.flatten() {
let _ = process_file(file);
}
#[cfg(target_os = "windows")]
{
let service_log_dir = log_dir.join("service");
for file in fs::read_dir(&service_log_dir)?.flatten() {
let _ = process_file(file);
}
}
Ok(())
}
+3 -5
View File
@@ -141,10 +141,7 @@ pub fn create_window(app_handle: &AppHandle) {
_ => {
#[cfg(target_os = "windows")]
{
builder = builder
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions")
.inner_size(800.0, 636.0)
.center();
builder = builder.inner_size(800.0, 636.0).center();
}
#[cfg(target_os = "macos")]
@@ -161,6 +158,7 @@ pub fn create_window(app_handle: &AppHandle) {
#[cfg(target_os = "windows")]
let window = builder
.decorations(false)
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
.transparent(true)
.visible(false)
.build();
@@ -171,7 +169,7 @@ pub fn create_window(app_handle: &AppHandle) {
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build();
#[cfg(target_os = "linux")]
let window = builder.decorations(true).transparent(false).build();
let window = builder.decorations(false).transparent(true).build();
match window {
Ok(win) => {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "Clash Verge",
"version": "1.5.7"
"version": "1.5.11"
},
"build": {
"distDir": "../dist",
+31 -6
View File
@@ -743,6 +743,30 @@ Function un.onInit
!insertmacro MUI_UNGETLANGUAGE
FunctionEnd
Function un.isDirectoryEmpty
Exch $0
Push $1
Push $2
StrCpy $2 0
FindFirst $1 $2 "$0\*.*"
loop:
StrCmp $2 "" done
StrCmp $2 "." next
StrCmp $2 ".." next
StrCpy $0 0
goto done
next:
FindNext $1 $2
goto loop
done:
FindClose $1
StrCmp $2 "" 0 +2
StrCpy $0 1
Pop $2
Pop $1
Exch $0
FunctionEnd
Section Uninstall
!insertmacro CheckIfAppIsRunning
@@ -756,7 +780,7 @@ Section Uninstall
{{#each resources}}
Delete "$INSTDIR\\{{this.[1]}}"
{{/each}}
Delete "$INSTDIR\resources"
; Delete external binaries
{{#each binaries}}
Delete "$INSTDIR\\{{this}}"
@@ -765,13 +789,14 @@ Section Uninstall
; Delete uninstaller
Delete "$INSTDIR\uninstall.exe"
${If} $DeleteAppDataCheckboxState == 1
; Remove InstallDir
Push "$INSTDIR"
Call un.isDirectoryEmpty
Pop $0
${If} $0 == 1
RMDir /R /REBOOTOK "$INSTDIR"
${Else}
{{#each resources_ancestors}}
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
{{/each}}
RMDir "$INSTDIR"
MessageBox MB_OK "Install Directory is not Empty, Please remove it manually."
${EndIf}
; Remove start menu shortcut
+5 -9
View File
@@ -47,11 +47,11 @@ body {
@import "./page.scss";
@import "./font.scss";
@media (prefers-color-scheme: dark) {
:root {
background-color: rgba(18, 18, 18, 1);
}
}
// @media (prefers-color-scheme: dark) {
// :root {
// background-color: rgba(18, 18, 18, 1);
// }
// }
.user-none {
user-select: none;
@@ -59,7 +59,3 @@ body {
-moz-user-select: none;
-ms-user-select: none;
}
[data-tauri-drag-region] {
app-region: drag;
}
+16 -6
View File
@@ -11,7 +11,8 @@
width: 100%;
// max-width: 225px;
// min-width: 225px;
padding: 16px 0 8px;
// padding: 16px 0 8px;
padding: 0px 0px 8px;
// position: relative;
flex-direction: column;
align-self: stretch;
@@ -32,12 +33,11 @@
// width: 100%;
display: flex;
height: 100%;
padding: 0px 20px;
flex-direction: column;
justify-content: center;
align-items: flex-start;
align-self: stretch;
border-bottom: 1px solid var(--divider-color);
// border-bottom: 1px solid var(--divider-color);
// max-width: $maxLogo + 32px;
// max-height: $maxLogo;
// margin: 0 auto;
@@ -60,7 +60,7 @@
.the-newbtn {
position: absolute;
right: 10px;
top: 0px;
top: 15px;
border-radius: 8px;
padding: 2px 4px;
transform: scale(0.8);
@@ -94,12 +94,16 @@
// position: absolute;
// top: 0px;
// right: 0px;
height: 24px;
height: 36px;
display: flex;
// align-items: center;
justify-content: end;
box-sizing: border-box;
z-index: 2;
.the-dragbar {
margin-top: 5px;
app-region: drag;
}
}
.the-content {
@@ -117,11 +121,17 @@
.unknown {
&.layout {
.layout__left {
padding-top: 24px;
// padding-top: 24px;
}
.layout__left .the-logo {
flex: 1 0 58px;
margin-top: 10px;
margin-left: 10px;
padding-top: 30px;
padding-left: 10px;
padding-right: 20px;
padding-bottom: 16px;
}
.layout__right .the-content {
+46
View File
@@ -0,0 +1,46 @@
import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from "@mui/material";
interface Props {
open: boolean;
title: string;
message: string;
onClose: () => void;
onConfirm: () => void;
}
export const ConfirmViewer = (props: Props) => {
const { open, title, message, onClose, onConfirm } = props;
const { t } = useTranslation();
useEffect(() => {
if (!open) return;
}, [open]);
return (
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
<DialogTitle>{t(title)}</DialogTitle>
<DialogContent sx={{ width: "95%", pb: 1, userSelect: "text" }}>
{t(message)}
</DialogContent>
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t("Cancel")}
</Button>
<Button onClick={onConfirm} variant="contained">
{t("Confirm")}
</Button>
</DialogActions>
</Dialog>
);
};
+46 -5
View File
@@ -23,6 +23,7 @@ import { Notice } from "@/components/base";
import { EditorViewer } from "./editor-viewer";
import { ProfileBox } from "./profile-box";
import parseTraffic from "@/utils/parse-traffic";
import { ConfirmViewer } from "./confirm-viewer";
const round = keyframes`
from { transform: rotate(0deg); }
@@ -92,6 +93,7 @@ export const ProfileItem = (props: Props) => {
}, [hasUrl, updated]);
const [fileOpen, setFileOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const onEditInfo = () => {
setAnchorEl(null);
@@ -171,14 +173,26 @@ export const ProfileItem = (props: Props) => {
{ label: "Open File", handler: onOpenFile },
{ label: "Update", handler: () => onUpdate(0) },
{ label: "Update(Proxy)", handler: () => onUpdate(2) },
{ label: "Delete", handler: onDelete },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
},
];
const fileModeMenu = [
{ label: "Select", handler: onForceSelect },
{ label: "Edit Info", handler: onEditInfo },
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{ label: "Delete", handler: onDelete },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
},
];
const boxStyle = {
@@ -243,6 +257,7 @@ export const ProfileItem = (props: Props) => {
<Typography
width="calc(100% - 36px)"
sx={{ fontSize: "18px", fontWeight: "600", lineHeight: "26px" }}
variant="h6"
component="h2"
noWrap
@@ -279,7 +294,11 @@ export const ProfileItem = (props: Props) => {
{
<>
{description ? (
<Typography noWrap title={description}>
<Typography
noWrap
title={description}
sx={{ fontSize: "14px" }}
>
{description}
</Typography>
) : (
@@ -312,7 +331,7 @@ export const ProfileItem = (props: Props) => {
<span title="Expire Time">{expire}</span>
</Box>
) : (
<Box sx={{ ...boxStyle, fontSize: 14, justifyContent: "flex-end" }}>
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
<span title="Updated Time">{parseExpire(updated)}</span>
</Box>
)}
@@ -336,7 +355,19 @@ export const ProfileItem = (props: Props) => {
<MenuItem
key={item.label}
onClick={item.handler}
sx={{ minWidth: 120 }}
sx={[
{
minWidth: 120,
},
(theme) => {
return {
color:
item.label === "Delete"
? theme.palette.error.main
: undefined,
};
},
]}
dense
>
{t(item.label)}
@@ -350,6 +381,16 @@ export const ProfileItem = (props: Props) => {
mode="yaml"
onClose={() => setFileOpen(false)}
/>
<ConfirmViewer
title="Confirm deletion"
message="This operation is not reversible"
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {
onDelete();
setConfirmOpen(false);
}}
/>
</Box>
);
};
+37 -4
View File
@@ -17,6 +17,7 @@ import { Notice } from "@/components/base";
import { EditorViewer } from "./editor-viewer";
import { ProfileBox } from "./profile-box";
import { LogViewer } from "./log-viewer";
import { ConfirmViewer } from "./confirm-viewer";
interface Props {
selected: boolean;
@@ -51,6 +52,7 @@ export const ProfileMore = (props: Props) => {
const [anchorEl, setAnchorEl] = useState<any>(null);
const [position, setPosition] = useState({ left: 0, top: 0 });
const [fileOpen, setFileOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const [logOpen, setLogOpen] = useState(false);
const onEditInfo = () => {
@@ -87,7 +89,13 @@ export const ProfileMore = (props: Props) => {
{ label: "Open File", handler: onOpenFile },
{ label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) },
{ label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) },
{ label: "Delete", handler: fnWrapper(onDelete) },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
},
];
const disableMenu = [
@@ -95,7 +103,13 @@ export const ProfileMore = (props: Props) => {
{ label: "Edit Info", handler: onEditInfo },
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{ label: "Delete", handler: fnWrapper(onDelete) },
{
label: "Delete",
handler: () => {
setAnchorEl(null);
setConfirmOpen(true);
},
},
];
const boxStyle = {
@@ -200,7 +214,17 @@ export const ProfileMore = (props: Props) => {
<MenuItem
key={item.label}
onClick={item.handler}
sx={{ minWidth: 120 }}
sx={[
{ minWidth: 120 },
(theme) => {
return {
color:
item.label === "Delete"
? theme.palette.error.main
: undefined,
};
},
]}
dense
>
{t(item.label)}
@@ -214,7 +238,16 @@ export const ProfileMore = (props: Props) => {
mode={type === "merge" ? "yaml" : "javascript"}
onClose={() => setFileOpen(false)}
/>
<ConfirmViewer
title="Confirm deletion"
message="This operation is not reversible"
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {
onDelete();
setConfirmOpen(false);
}}
/>
{selected && (
<LogViewer
open={logOpen}
+3 -1
View File
@@ -51,7 +51,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
desc: "",
url: "",
option: {
// user_agent: "",
with_proxy: false,
self_proxy: false,
},
@@ -99,6 +98,9 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
if (form.option?.update_interval) {
form.option.update_interval = +form.option.update_interval;
}
if (form.option?.user_agent === "") {
delete form.option.user_agent;
}
const name = form.name || `${form.type} file`;
const item = { ...form, name };
+25 -4
View File
@@ -19,6 +19,9 @@ import type { IRenderItem } from "./use-render-list";
import { useVerge } from "@/hooks/use-verge";
import { useRecoilState } from "recoil";
import { atomThemeMode } from "@/services/states";
import { useEffect, useState } from "react";
import { convertFileSrc } from "@tauri-apps/api/tauri";
import { downloadIconCache } from "@/services/cmds";
interface RenderProps {
item: IRenderItem;
@@ -38,6 +41,24 @@ export const ProxyRender = (props: RenderProps) => {
const [mode] = useRecoilState(atomThemeMode);
const isDark = mode === "light" ? false : true;
const itembackgroundcolor = isDark ? "#282A36" : "#ffffff";
const [iconCachePath, setIconCachePath] = useState("");
useEffect(() => {
initIconCachePath();
}, [group]);
async function initIconCachePath() {
if (group.icon && group.icon.trim().startsWith("http")) {
const fileName =
group.name.replaceAll(" ", "") + "-" + getFileName(group.icon);
const iconPath = await downloadIconCache(group.icon, fileName);
setIconCachePath(convertFileSrc(iconPath));
}
}
function getFileName(url: string) {
return url.substring(url.lastIndexOf("/") + 1);
}
if (type === 0 && !group.hidden) {
return (
@@ -55,8 +76,8 @@ export const ProxyRender = (props: RenderProps) => {
group.icon &&
group.icon.trim().startsWith("http") && (
<img
src={group.icon}
height="32px"
src={iconCachePath === "" ? group.icon : iconCachePath}
width="32px"
style={{ marginRight: "12px", borderRadius: "6px" }}
/>
)}
@@ -65,7 +86,7 @@ export const ProxyRender = (props: RenderProps) => {
group.icon.trim().startsWith("data") && (
<img
src={group.icon}
height="32px"
width="32px"
style={{ marginRight: "12px", borderRadius: "6px" }}
/>
)}
@@ -74,7 +95,7 @@ export const ProxyRender = (props: RenderProps) => {
group.icon.trim().startsWith("<svg") && (
<img
src={`data:image/svg+xml;base64,${btoa(group.icon)}`}
height="32px"
width="32px"
/>
)}
<ListItemText
+1 -1
View File
@@ -20,7 +20,7 @@ interface Props {
}
const parseColor = (text: string) => {
if (text === "REJECT") return "error.main";
if (text === "REJECT" || text === "REJECT-DROP") return "error.main";
if (text === "DIRECT") return "text.primary";
let sum = 0;
@@ -5,6 +5,8 @@ import { List, ListItem, ListItemText, TextField } from "@mui/material";
import { useClashInfo } from "@/hooks/use-clash";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { useVerge } from "@/hooks/use-verge";
import getSystem from "@/utils/get-system";
const OS = getSystem();
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -13,6 +15,12 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
const { verge, patchVerge } = useVerge();
const [open, setOpen] = useState(false);
const [redirPort, setRedirPort] = useState(
verge?.verge_redir_port ?? clashInfo?.redir_port ?? 7895
);
const [tproxyPort, setTproxyPort] = useState(
verge?.verge_tproxy_port ?? clashInfo?.tproxy_port ?? 7896
);
const [mixedPort, setMixedPort] = useState(
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
);
@@ -25,6 +33,8 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
useImperativeHandle(ref, () => ({
open: () => {
if (verge?.verge_redir_port) setRedirPort(verge?.verge_redir_port);
if (verge?.verge_tproxy_port) setTproxyPort(verge?.verge_tproxy_port);
if (verge?.verge_mixed_port) setMixedPort(verge?.verge_mixed_port);
if (verge?.verge_socks_port) setSocksPort(verge?.verge_socks_port);
if (verge?.verge_port) setPort(verge?.verge_port);
@@ -35,6 +45,8 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
const onSave = useLockFn(async () => {
if (
redirPort === verge?.verge_redir_port &&
tproxyPort === verge?.verge_tproxy_port &&
mixedPort === verge?.verge_mixed_port &&
socksPort === verge?.verge_socks_port &&
port === verge?.verge_port
@@ -42,7 +54,34 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
setOpen(false);
return;
}
if (
OS === "linux" &&
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
) {
Notice.error("Port conflict!", 4000);
return;
}
if (
OS === "macos" &&
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
) {
Notice.error("Port conflict!", 4000);
return;
}
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
Notice.error("Port conflict!", 4000);
return;
}
try {
if (OS !== "windows") {
await patchInfo({ "redir-port": redirPort });
await patchVerge({ verge_redir_port: redirPort });
}
if (OS === "linux") {
await patchInfo({ "tproxy-port": tproxyPort });
await patchVerge({ verge_tproxy_port: tproxyPort });
}
await patchInfo({ "mixed-port": mixedPort });
await patchInfo({ "socks-port": socksPort });
await patchInfo({ port });
@@ -68,6 +107,35 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
onOk={onSave}
>
<List>
{OS !== "windows" && (
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary="Redir Port" />
<TextField
size="small"
autoComplete="off"
sx={{ width: 135 }}
value={redirPort}
onChange={(e) =>
setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
/>
</ListItem>
)}
{OS === "linux" && (
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary="Tproxy Port" />
<TextField
size="small"
autoComplete="off"
sx={{ width: 135 }}
value={tproxyPort}
onChange={(e) =>
setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
}
/>
</ListItem>
)}
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary="Mixed Port" />
<TextField
@@ -19,7 +19,6 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
const [sysproxyIcon, setSysproxyIcon] = useState("");
const [tunIcon, setTunIcon] = useState("");
// const { menu_icon } = verge ?? {};
useEffect(() => {
initIconPath();
}, []);
+20 -1
View File
@@ -10,7 +10,13 @@ import {
Input,
Typography,
} from "@mui/material";
import { exitApp, openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
import {
exitApp,
openAppDir,
openCoreDir,
openLogsDir,
openDevTools,
} from "@/services/cmds";
import { ArrowForward } from "@mui/icons-material";
import { checkUpdate } from "@tauri-apps/api/updater";
import { useVerge } from "@/hooks/use-verge";
@@ -27,6 +33,7 @@ import { LayoutViewer } from "./mods/layout-viewer";
import { UpdateViewer } from "./mods/update-viewer";
import getSystem from "@/utils/get-system";
import { routers } from "@/pages/_routers";
interface Props {
onError?: (err: Error) => void;
}
@@ -90,6 +97,7 @@ const SettingVerge = ({ onError }: Props) => {
<MenuItem value="zh">中文</MenuItem>
<MenuItem value="en">English</MenuItem>
<MenuItem value="ru">Русский</MenuItem>
<MenuItem value="fa">فارسی</MenuItem>
</Select>
</GuardState>
</SettingItem>
@@ -304,6 +312,17 @@ const SettingVerge = ({ onError }: Props) => {
</IconButton>
</SettingItem>
<SettingItem label={t("Open Dev Tools")}>
<IconButton
color="inherit"
size="small"
sx={{ my: "2px" }}
onClick={openDevTools}
>
<ArrowForward />
</IconButton>
</SettingItem>
<SettingItem label={t("Exit")}>
<IconButton
color="inherit"
+24 -3
View File
@@ -17,8 +17,9 @@ import { LanguageTwoTone } from "@mui/icons-material";
import { Notice } from "@/components/base";
import { TestBox } from "./test-box";
import delayManager from "@/services/delay";
import { cmdTestDelay } from "@/services/cmds";
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
import { listen, UnlistenFn } from "@tauri-apps/api/event";
import { convertFileSrc } from "@tauri-apps/api/tauri";
interface Props {
id: string;
@@ -39,6 +40,23 @@ export const TestItem = (props: Props) => {
const [position, setPosition] = useState({ left: 0, top: 0 });
const [delay, setDelay] = useState(-1);
const { uid, name, icon, url } = itemData;
const [iconCachePath, setIconCachePath] = useState("");
useEffect(() => {
initIconCachePath();
}, [icon]);
async function initIconCachePath() {
if (icon && icon.trim().startsWith("http")) {
const fileName = uid + "-" + getFileName(icon);
const iconPath = await downloadIconCache(icon, fileName);
setIconCachePath(convertFileSrc(iconPath));
}
}
function getFileName(url: string) {
return url.substring(url.lastIndexOf("/") + 1);
}
const onDelay = async () => {
setDelay(-2);
@@ -104,7 +122,10 @@ export const TestItem = (props: Props) => {
{icon && icon.trim() !== "" ? (
<Box sx={{ display: "flex", justifyContent: "center" }}>
{icon.trim().startsWith("http") && (
<img src={icon} height="40px" />
<img
src={iconCachePath === "" ? icon : iconCachePath}
height="40px"
/>
)}
{icon.trim().startsWith("data") && (
<img src={icon} height="40px" />
+29 -1
View File
@@ -49,11 +49,19 @@ export const useClashInfo = () => {
patch: Partial<
Pick<
IConfigData,
"port" | "socks-port" | "mixed-port" | "external-controller" | "secret"
| "port"
| "socks-port"
| "mixed-port"
| "redir-port"
| "tproxy-port"
| "external-controller"
| "secret"
>
>
) => {
const hasInfo =
patch["redir-port"] != null ||
patch["tproxy-port"] != null ||
patch["mixed-port"] != null ||
patch["socks-port"] != null ||
patch["port"] != null ||
@@ -62,6 +70,26 @@ export const useClashInfo = () => {
if (!hasInfo) return;
if (patch["redir-port"]) {
const port = patch["redir-port"];
if (port < 1000) {
throw new Error("The port should not < 1000");
}
if (port > 65536) {
throw new Error("The port should not > 65536");
}
}
if (patch["tproxy-port"]) {
const port = patch["tproxy-port"];
if (port < 1000) {
throw new Error("The port should not < 1000");
}
if (port > 65536) {
throw new Error("The port should not > 65536");
}
}
if (patch["mixed-port"]) {
const port = patch["mixed-port"];
if (port < 1000) {
+1
View File
@@ -0,0 +1 @@
{}
+4
View File
@@ -45,6 +45,8 @@
"Update All Profiles": "更新所有订阅",
"View Runtime Config": "查看运行时订阅",
"Reactivate Profiles": "重新激活订阅",
"Confirm deletion": "确认删除",
"This operation is not reversible": "此操作不可逆",
"Location": "当前节点",
"Delay check": "延迟测试",
@@ -123,6 +125,7 @@
"Open Core Dir": "内核目录",
"Open Logs Dir": "日志目录",
"Check for Updates": "检查更新",
"Open Dev Tools": "打开开发者工具",
"Verge Version": "Verge 版本",
"theme.light": "浅色",
"theme.dark": "深色",
@@ -137,6 +140,7 @@
"Save": "保存",
"Cancel": "取消",
"Exit": "退出",
"Confirm": "确认",
"Default": "默认",
"Download Speed": "下载速度",
+18 -5
View File
@@ -122,9 +122,17 @@ const Layout = () => {
({ palette }) => ({
bgcolor: palette.background.paper,
}),
OS === "linux"
? {
borderRadius: "8px",
border: "2px solid var(--divider-color)",
width: "calc(100vw - 4px)",
height: "calc(100vh - 4px)",
}
: {},
]}
>
<div className="layout__left" data-tauri-drag-region="true">
<div className="layout__left">
<div className="the-logo" data-tauri-drag-region="true">
{!isDark ? <LogoSvg /> : <LogoSvg_dark />}
{<UpdateButton className="the-newbtn" />}
@@ -148,11 +156,16 @@ const Layout = () => {
</div>
<div className="layout__right">
{OS === "windows" && (
<div className="the-bar" data-tauri-drag-region="true">
<LayoutControl />
{
<div className="the-bar">
<div
className="the-dragbar"
data-tauri-drag-region="true"
style={{ width: "100%" }}
></div>
{OS !== "macos" && <LayoutControl />}
</div>
)}
}
<TransitionGroup className="the-content">
<CSSTransition
+3 -3
View File
@@ -4,7 +4,7 @@ const OS = getSystem();
// default theme setting
export const defaultTheme = {
primary_color: "#007AFF",
secondary_color: "#FFCC00",
secondary_color: "#fc9b76",
primary_text: "#000000",
secondary_text: "#3c3c4399",
info_color: "#007AFF",
@@ -12,8 +12,8 @@ export const defaultTheme = {
warning_color: "#FF9500",
success_color: "#06943D",
background_color: "#f5f5f5",
font_family: `-apple-system, BlinkMacSystemFont,"Microsoft YaHei UI", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", ${
OS === "windows" ? "twemoji mozilla" : ""
font_family: `-apple-system, BlinkMacSystemFont,"Microsoft YaHei UI", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji"${
OS === "windows" ? ", twemoji mozilla" : ""
}`,
};
+2 -2
View File
@@ -200,10 +200,10 @@ const ConnectionsPage = () => {
</Box>
<Box
height="calc(100% - 70px)"
height="calc(100% - 65px)"
sx={{
userSelect: "text",
margin: "12px",
margin: "10px",
borderRadius: "8px",
bgcolor: isDark ? "#282a36" : "#ffffff",
}}
+2 -2
View File
@@ -110,9 +110,9 @@ const LogPage = () => {
</Box>
<Box
height="calc(100% - 70px)"
height="calc(100% - 65px)"
sx={{
margin: "12px",
margin: "10px",
borderRadius: "8px",
bgcolor: isDark ? "#282a36" : "#ffffff",
}}
+48 -21
View File
@@ -2,7 +2,15 @@ import useSWR, { mutate } from "swr";
import { useMemo, useRef, useState } from "react";
import { useLockFn } from "ahooks";
import { useSetRecoilState } from "recoil";
import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material";
import {
Box,
Button,
Grid,
IconButton,
Stack,
TextField,
Divider,
} from "@mui/material";
import {
DndContext,
closestCenter,
@@ -46,6 +54,8 @@ import { ProfileMore } from "@/components/profile/profile-more";
import { useProfiles } from "@/hooks/use-profiles";
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
import { throttle } from "lodash-es";
import { useRecoilState } from "recoil";
import { atomThemeMode } from "@/services/states";
const ProfilePage = () => {
const { t } = useTranslation();
@@ -235,6 +245,11 @@ const ProfilePage = () => {
const text = await navigator.clipboard.readText();
if (text) setUrl(text);
};
const [mode] = useRecoilState(atomThemeMode);
const islight = mode === "light" ? true : false;
const dividercolor = islight
? "rgba(0, 0, 0, 0.06)"
: "rgba(255, 255, 255, 0.06)";
return (
<BasePage
@@ -323,6 +338,7 @@ const ProfilePage = () => {
loading={loading}
variant="contained"
size="small"
sx={{ borderRadius: "6px" }}
onClick={onImport}
>
{t("Import")}
@@ -330,6 +346,7 @@ const ProfilePage = () => {
<Button
variant="contained"
size="small"
sx={{ borderRadius: "6px" }}
onClick={() => viewerRef.current?.create()}
>
{t("New")}
@@ -341,7 +358,7 @@ const ProfilePage = () => {
mb: 0.5,
pl: "10px",
mr: "10px",
height: "calc(100% - 20px)",
height: "calc(100% - 68px)",
overflowY: "auto",
}}
>
@@ -350,7 +367,7 @@ const ProfilePage = () => {
collisionDetection={closestCenter}
onDragEnd={onDragEnd}
>
<Box sx={{ mb: 4.5 }}>
<Box sx={{ mb: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
<SortableContext
items={regularItems.map((x) => {
@@ -375,24 +392,34 @@ const ProfilePage = () => {
</DndContext>
{enhanceItems.length > 0 && (
<Grid container spacing={{ xs: 2, lg: 2 }}>
{enhanceItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileMore
selected={!!chain.includes(item.uid)}
itemData={item}
enableNum={chain.length || 0}
logInfo={chainLogs[item.uid]}
onEnable={() => onEnable(item.uid)}
onDisable={() => onDisable(item.uid)}
onDelete={() => onDelete(item.uid)}
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
onEdit={() => viewerRef.current?.edit(item)}
/>
</Grid>
))}
</Grid>
<Divider
variant="middle"
flexItem
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
></Divider>
)}
{enhanceItems.length > 0 && (
<Box sx={{ mt: 1.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
{enhanceItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileMore
selected={!!chain.includes(item.uid)}
itemData={item}
enableNum={chain.length || 0}
logInfo={chainLogs[item.uid]}
onEnable={() => onEnable(item.uid)}
onDisable={() => onDisable(item.uid)}
onDelete={() => onDelete(item.uid)}
onMoveTop={() => onMoveTop(item.uid)}
onMoveEnd={() => onMoveEnd(item.uid)}
onEdit={() => viewerRef.current?.edit(item)}
/>
</Grid>
))}
</Grid>
</Box>
)}
</Box>
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
+2 -2
View File
@@ -56,9 +56,9 @@ const RulesPage = () => {
</Box>
<Box
height="calc(100% - 70px)"
height="calc(100% - 65px)"
sx={{
margin: "12px",
margin: "10px",
borderRadius: "8px",
bgcolor: isDark ? "#282a36" : "#ffffff",
}}
+1 -1
View File
@@ -134,7 +134,7 @@ const TestPage = () => {
>
<Box
sx={{
pt: 1,
pt: 1.25,
mb: 0.5,
px: "10px",
}}
+8
View File
@@ -213,6 +213,10 @@ export async function getPortableFlag() {
return invoke<boolean>("get_portable_flag");
}
export async function openDevTools() {
return invoke("open_devtools");
}
export async function exitApp() {
return invoke("exit_app");
}
@@ -223,3 +227,7 @@ export async function copyIconFile(
) {
return invoke<void>("copy_icon_file", { path, name });
}
export async function downloadIconCache(url: string, name: string) {
return invoke<string>("download_icon_cache", { url, name });
}
+3
View File
@@ -3,16 +3,19 @@ import { initReactI18next } from "react-i18next";
import en from "@/locales/en.json";
import ru from "@/locales/ru.json";
import zh from "@/locales/zh.json";
import fa from "@/locales/fa.json";
const resources = {
en: { translation: en },
ru: { translation: ru },
zh: { translation: zh },
fa: { translation: fa },
};
i18n.use(initReactI18next).init({
resources,
lng: "en",
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
+4
View File
@@ -142,6 +142,8 @@ interface IClashInfo {
// status: string;
mixed_port?: number; // clash mixed port
socks_port?: number; // clash socks port
redir_port?: number; // clash redir port
tproxy_port?: number; // clash tproxy port
port?: number; // clash http port
server?: string; // external-controller
secret?: string;
@@ -214,6 +216,8 @@ interface IVergeConfig {
enable_random_port?: boolean;
verge_mixed_port?: number;
verge_socks_port?: number;
verge_redir_port?: number;
verge_tproxy_port?: number;
verge_port?: number;
enable_proxy_guard?: boolean;
proxy_guard_duration?: number;