mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-15 13:50:47 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
787463a226 | ||
|
|
8d5d72957b | ||
|
|
7d805cb83e | ||
|
|
a1286ad057 | ||
|
|
9713343323 | ||
|
|
b282217e5c | ||
|
|
4b29a140b5 | ||
|
|
b6af7b7440 | ||
|
|
bcb8e831c5 | ||
|
|
a9e6391417 | ||
|
|
721929a2a1 | ||
|
|
b35d0ac16f | ||
|
|
3198c3f023 | ||
|
|
a3df3fc2a6 | ||
|
|
507d85ac2f | ||
|
|
236e5c56c3 | ||
|
|
5b63f350ed | ||
|
|
5f885bbe32 | ||
|
|
6e170993dc | ||
|
|
ee6b857336 | ||
|
|
195f470bca | ||
|
|
37c2428f49 | ||
|
|
34793f9880 | ||
|
|
04e50824f1 | ||
|
|
da7faf815f | ||
|
|
af9b67186e | ||
|
|
c5fa64a213 | ||
|
|
5c42658a2e | ||
|
|
3c1bcd7d72 | ||
|
|
328803b894 | ||
|
|
fbe44cf9c1 | ||
|
|
6db559f346 | ||
|
|
f0c7aca3ff | ||
|
|
8316c75c78 | ||
|
|
e56a989944 | ||
|
|
cea78289c1 | ||
|
|
1a2d45555d | ||
|
|
11879e224c | ||
|
|
c8628f32df | ||
|
|
1bb1cb3511 | ||
|
|
711aaf05d3 | ||
|
|
430c946b61 | ||
|
|
2995f61087 | ||
|
|
2aaa91f4cb | ||
|
|
104fd11b04 | ||
|
|
c7dd93004e | ||
|
|
86dcf0bdcf | ||
|
|
09a4e7e083 | ||
|
|
5bb4539e3f | ||
|
|
cad1cf2895 | ||
|
|
623ed69a37 | ||
|
|
7838fa1e75 | ||
|
|
dd34ced070 | ||
|
|
66483d0ef6 | ||
|
|
54858ce1fb | ||
|
|
dfa5cff1b7 | ||
|
|
2b552d6ddb | ||
|
|
af7b33d35b | ||
|
|
d8e386e394 | ||
|
|
bae65a523a | ||
|
|
aa767695af | ||
|
|
494e888b52 | ||
|
|
5292067284 | ||
|
|
9286e921a6 | ||
|
|
3ab985aacc | ||
|
|
418fc1c702 | ||
|
|
b5da9c9629 | ||
|
|
86936d5322 | ||
|
|
c1489647d1 | ||
|
|
af0a3be66d |
@@ -11,11 +11,11 @@ body:
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
|
||||
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填写一个简洁明了的标题,以便他人快速检索
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似 issue,否则请在已有的 issue 下进行讨论
|
||||
3. 请 **务必** 给 issue 填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 查看 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本更新日志
|
||||
5. 请 **务必** 尝试 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本,确定问题是否仍然存在
|
||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本,否则issue将会被直接关闭
|
||||
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 AutoBuild 版本,否则 issue 将会被直接关闭
|
||||
|
||||
## Before submitting the issue, please make sure of the following checklist:
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ body:
|
||||
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下进行讨论
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似 issue,否则请在已有的 issue 下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) 版本测试,确保该功能还未实现
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
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
|
||||
|
||||
@@ -307,7 +307,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -377,7 +377,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -505,7 +505,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -187,11 +187,11 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -283,11 +283,11 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -433,11 +433,11 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -535,7 +535,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4.2.0
|
||||
name: Install pnpm
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -103,11 +103,11 @@ jobs:
|
||||
if: github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (macOS)
|
||||
if: matrix.os == 'macos-latest' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/bundle/dmg/*.dmg
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (Windows)
|
||||
if: matrix.os == 'windows-latest' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/bundle/nsis/*.exe
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (Linux)
|
||||
if: matrix.os == 'ubuntu-22.04' && github.event.inputs[matrix.input] == 'true'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
@@ -47,12 +47,12 @@ jobs:
|
||||
- uses: actions/setup-node@v6
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Restore pnpm cache
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.pnpm-store
|
||||
key: "pnpm-shared-stable-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
|
||||
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -281,7 +281,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -420,7 +420,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -505,7 +505,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -531,7 +531,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -593,7 +593,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24.11.1"
|
||||
node-version: "24.12.0"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
+7
-13
@@ -28,24 +28,18 @@ pnpm exec lint-staged
|
||||
RUST_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src-tauri/.*\.rs$' || true)"
|
||||
if [ -n "$RUST_FILES" ]; then
|
||||
echo "[pre-commit] Formatting Rust changes with cargo fmt..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo fmt
|
||||
)
|
||||
cargo fmt
|
||||
while IFS= read -r file; do
|
||||
[ -n "$file" ] && git add "$file"
|
||||
done <<< "$RUST_FILES"
|
||||
|
||||
echo "[pre-commit] Linting Rust changes with cargo clippy..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo clippy-all
|
||||
if ! command -v clash-verge-logging-check >/dev/null 2>&1; then
|
||||
echo "[pre-commit] Installing clash-verge-logging-check..."
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-logging-check.git
|
||||
fi
|
||||
clash-verge-logging-check
|
||||
)
|
||||
cargo clippy-all
|
||||
if ! command -v clash-verge-logging-check >/dev/null 2>&1; then
|
||||
echo "[pre-commit] Installing clash-verge-logging-check..."
|
||||
cargo install --git https://github.com/clash-verge-rev/clash-verge-logging-check.git
|
||||
fi
|
||||
clash-verge-logging-check
|
||||
fi
|
||||
|
||||
TS_FILES="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx)$' || true)"
|
||||
|
||||
+2
-8
@@ -25,16 +25,10 @@ pnpm typecheck
|
||||
|
||||
if command -v cargo >/dev/null 2>&1; then
|
||||
echo "[pre-push] Verifying Rust formatting..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo fmt --check
|
||||
)
|
||||
cargo fmt --check
|
||||
|
||||
echo "[pre-push] Running cargo clippy..."
|
||||
(
|
||||
cd src-tauri
|
||||
cargo clippy-all
|
||||
)
|
||||
cargo clippy-all
|
||||
else
|
||||
echo "[pre-push] ⚠️ cargo not found; skipping Rust checks."
|
||||
fi
|
||||
|
||||
+5
-1
@@ -119,13 +119,17 @@ cargo fmt
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### Signing your commit
|
||||
|
||||
Signed commits are required to verify authorship and ensure your contributions can be merged. Reference signing-commits [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).
|
||||
|
||||
### Submitting Your Changes
|
||||
|
||||
1. Fork the repository.
|
||||
|
||||
2. Create a new branch for your feature or bug fix.
|
||||
|
||||
3. Commit your changes with clear messages.
|
||||
3. Commit your changes with clear messages and make sure it's signed.
|
||||
|
||||
4. Push your branch and submit a pull request.
|
||||
|
||||
|
||||
Generated
+61
-77
@@ -1002,9 +1002,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
|
||||
checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -1059,9 +1059,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.48"
|
||||
version = "1.2.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -1186,7 +1186,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.4.4-rc"
|
||||
version = "2.4.4"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -1312,8 +1312,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash_verge_service_ipc"
|
||||
version = "2.0.24"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#c5a557d5a9f15264697a8ac70d42946a881598d7"
|
||||
version = "2.0.26"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc#37b9964a9bce767b5b95ea2be75613b23400c9f0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"compact_str",
|
||||
@@ -2134,9 +2134,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d65cde5fb0c42a3d5882d99807698b459f5928de035fa7f547c784fb7b34219"
|
||||
checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"
|
||||
dependencies = [
|
||||
"dlopen2_derive",
|
||||
"libc",
|
||||
@@ -2146,9 +2146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2_derive"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95f4a04e1bfbfa4835a6073177aafb95ead4de0722dbb339195fdc7e0a09599b"
|
||||
checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2497,13 +2497,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.7"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4035,9 +4035,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kode-bridge"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b158d8b9ab9f07c892a3f1b5d44a79e6c74e97541bf66488025796d429cf7aac"
|
||||
checksum = "0ba2fd0531052f4438d171509ee7686b6b2207314366a5e2dde80586f6d6a09b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
@@ -4155,6 +4155,15 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
@@ -4423,9 +4432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.10"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
|
||||
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
@@ -4804,7 +4813,7 @@ dependencies = [
|
||||
"objc2-core-text",
|
||||
"objc2-core-video",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-quartz-core 0.3.2",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4959,18 +4968,6 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.3.2"
|
||||
@@ -4983,19 +4980,6 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
@@ -5004,6 +4988,7 @@ checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
@@ -5767,7 +5752,7 @@ version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.8",
|
||||
"toml_edit 0.23.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6261,9 +6246,9 @@ checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.24"
|
||||
version = "0.12.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
|
||||
checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -6298,7 +6283,7 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.6.7",
|
||||
"tower-http 0.6.8",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -7099,9 +7084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@@ -7197,24 +7182,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "softbuffer"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
|
||||
checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"core-graphics",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
"ndk",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle",
|
||||
"redox_syscall",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7400,8 +7385,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysproxy"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#0f844dd2639b0ac74da4548b1325335844947420"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#51256d5921a01bbcf24fe5629296d2bfe493329d"
|
||||
dependencies = [
|
||||
"interfaces",
|
||||
"iptools",
|
||||
@@ -7530,9 +7515,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.9.4"
|
||||
version = "2.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15524fc7959bfcaa051ba6d0b3fb1ef18e978de2176c7c6acb977f7fd14d35c7"
|
||||
checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7833,7 +7818,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-mihomo"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#85fc6b364227e69f5b1cfeb8ca82642c153b5210"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#153f16f7b3f979aa130a2d3d7c39a52a39987288"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"futures-util",
|
||||
@@ -7979,9 +7964,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.9.2"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7950f3bde6bcca6655bc5e76d3d6ec587ceb81032851ab4ddbe1f508bdea2729"
|
||||
checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.4.0",
|
||||
@@ -8490,9 +8475,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.8"
|
||||
version = "0.23.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9b7ac41d92f2d2803f233e297127bac397df7b337e0460a1cc39d6c006dee4"
|
||||
checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.7.3",
|
||||
@@ -8680,9 +8665,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.7"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bytes",
|
||||
@@ -10080,15 +10065,14 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.9.2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb"
|
||||
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"os_pipe",
|
||||
"rustix 0.38.44",
|
||||
"tempfile",
|
||||
"rustix 1.1.2",
|
||||
"thiserror 2.0.17",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
@@ -10448,9 +10432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36134c44663532e6519d7a6dfdbbe06f6f8192bde8ae9ed076e9b213f0e31df7"
|
||||
checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
|
||||
+1
-4
@@ -9,9 +9,6 @@ members = [
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
@@ -49,7 +46,7 @@ clash-verge-signal = { path = "crates/clash-verge-signal" }
|
||||
clash-verge-types = { path = "crates/clash-verge-types" }
|
||||
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
|
||||
|
||||
tauri = { version = "2.9.4" }
|
||||
tauri = { version = "2.9.5" }
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
||||
anyhow = "1.0.100"
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
- 修复 macOS 在安装和卸载服务时提示与操作不匹配
|
||||
- 修复菜单排序模式拖拽异常
|
||||
- 修复托盘菜单代理组前的异常勾选状态
|
||||
- 修复 Windows 下自定义标题栏按钮在最小化 / 关闭后 hover 状态残留
|
||||
- 修复直接覆盖 `config.yaml` 使用时无法展开代理组
|
||||
- 修复 macOS 下应用启动时系统托盘图标颜色闪烁
|
||||
- 修复应用静默启动模式下非全局热键一直抢占其他应用按键问题
|
||||
- 修复首页当前节点卡片按延迟排序时,打开节点列表后,`timeout` 节点被排在正常节点前的问题
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
@@ -36,6 +41,8 @@
|
||||
- 日志页面支持按时间倒序
|
||||
- 增加「重新激活订阅」的全局快捷键
|
||||
- WebView2 Runtime 修复构建升级到 133.0.3065.92
|
||||
- 侧边栏右键新增「恢复默认排序」
|
||||
- Linux 下新增对 TUN 「自动重定向」(`auto-redirect` 字段)的配置支持,默认关闭
|
||||
|
||||
</details>
|
||||
|
||||
@@ -61,6 +68,8 @@
|
||||
- 优化前端 WebSocket 连接机制
|
||||
- 改进旧版 Service 需要重新安装检测流程
|
||||
- 优化 macOS, Linux 和 Windows 系统信号处理
|
||||
- 链式代理仅显示 Selector 类型规则组
|
||||
- 优化 Windows 系统代理设置,不再依赖 `sysproxy.exe` 来设置代理
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
|
||||
<a href="./docs/README_es.md">Español</a> ·
|
||||
<a href="./docs/README_ru.md">Русский</a> ·
|
||||
<a href="./docs/README_ja.md">日本語</a> ·
|
||||
<a href="./docs/README_ko.md">한국어</a>
|
||||
<a href="./docs/README_ko.md">한국어</a> ·
|
||||
<a href="./docs/README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Preview
|
||||
@@ -47,17 +48,23 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
|
||||
## Promotion
|
||||
|
||||
#### [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
### ✈️ [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
||||
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
||||
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
||||
- 海外团队,无跑路风险,高达 50% 返佣
|
||||
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
||||
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
|
||||
- 解锁流媒体及 ChatGPT
|
||||
- 官网:[https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
🚀 高性能海外技术流机场,支持免费试用与优惠套餐,全面解锁流媒体及 AI 服务,全球首家采用 **QUIC 协议**。
|
||||
|
||||
🎁 使用 **Clash Verge 专属邀请链接** 注册即送 **3 天免费试用**,每日 **1GB 流量**:👉 [点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### **核心优势:**
|
||||
|
||||
- 📱 自研 iOS 客户端(业内"唯一")技术经得起考验,极大**持续研发**投入
|
||||
- 🧑💻 **12小时真人客服**(顺带解决 Clash Verge 使用问题)
|
||||
- 💰 优惠套餐每月**仅需 21 元,160G 流量,年付 8 折**
|
||||
- 🌍 海外团队,无跑路风险,高达 50% 返佣
|
||||
- ⚙️ **集群负载均衡**设计,**负载监控和随时扩容**,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
|
||||
- ⚡ 全球首家**Quic 协议机场**,现已上线更快的 Tuic 协议(Clash Verge 客户端最佳搭配)
|
||||
- 🎬 解锁**流媒体及 主流 AI**
|
||||
|
||||
🌐 官网:👉 [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### 本项目的构建与发布环境由 [YXVM](https://yxvm.com/aff.php?aff=827) 独立服务器全力支持,
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "clash-verge-signal"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
clash-verge-logging = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "clash-verge-types"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "tauri-plugin-clash-verge-sysinfo"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true }
|
||||
|
||||
+18
-11
@@ -16,7 +16,8 @@ A Clash Meta GUI built with <a href="https://github.com/tauri-apps/tauri">Tauri<
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Preview
|
||||
@@ -50,17 +51,23 @@ Join [@clash_verge_rev](https://t.me/clash_verge_re) for update announcements.
|
||||
|
||||
## Promotion
|
||||
|
||||
#### [Doggygo VPN — Performance-oriented global accelerator](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
### ✈️ [Doggygo VPN — A Technical-Grade Proxy Service](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- High-performance overseas network service with free trials, discounted plans, streaming unlocks, and first-class Hysteria protocol support.
|
||||
- Register through the exclusive Clash Verge link to get a 3-day trial with 1 GB of traffic per day: [Sign up](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- Exclusive 20% off coupon for Clash Verge users: `verge20` (limited to 500 uses)
|
||||
- Discounted bundle from ¥15.8 per month for 160 GB, plus an additional 20% off for yearly billing
|
||||
- Operated by an overseas team with reliable service and up to 50% revenue share
|
||||
- Load-balanced clusters with high-speed dedicated routes (compatible with legacy clients), exceptionally low latency, smooth 4K playback
|
||||
- First global provider to support the `Hysteria2` protocol—perfect fit for the Clash Verge client
|
||||
- Supports streaming services and ChatGPT access
|
||||
- Official site: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
🚀 A high-performance, overseas, technical-grade proxy service offering free trials and discounted plans, fully unlocking streaming platforms and AI services. The world’s first provider to adopt the **QUIC protocol**.
|
||||
|
||||
🎁 Register via the **Clash Verge exclusive invitation link** to receive **3 days of free trial**, with **1GB traffic per day**: 👉 [Register here](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### **Core Advantages:**
|
||||
|
||||
- 📱 Self-developed iOS client (the industry’s “only one”), with technology proven in production and **significant ongoing R&D investment**
|
||||
- 🧑💻 **12-hour live customer support** (also assists with Clash Verge usage issues)
|
||||
- 💰 Discounted plans at **only CNY 21 per month, 160GB traffic, 20% off with annual billing**
|
||||
- 🌍 Overseas team, no risk of shutdown or exit scams, with up to **50% referral commission**
|
||||
- ⚙️ **Cluster-based load balancing** architecture with **real-time load monitoring and elastic scaling**, high-speed dedicated lines (compatible with legacy clients), ultra-low latency, unaffected by peak hours, **4K streaming loads instantly**
|
||||
- ⚡ The world’s first **QUIC-protocol-based proxy service**, now upgraded with the faster **Tuic protocol** (best paired with the Clash Verge client)
|
||||
- 🎬 Unlocks **streaming platforms and mainstream AI services**
|
||||
|
||||
🌐 Official Website: 👉 [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### Build Infrastructure Sponsor — [YXVM Dedicated Servers](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ Una interfaz gráfica para Clash Meta construida con <a href="https://github.com
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## Vista previa
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<h1 align="center">
|
||||
<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>
|
||||
</h1>
|
||||
|
||||
<h3 align="center">
|
||||
یک رابط کاربری گرافیکی Clash Meta که با <a href="https://github.com/tauri-apps/tauri">Tauri</a> ساخته شده است.
|
||||
</h3>
|
||||
|
||||
<p align="center">
|
||||
زبانها:
|
||||
<a href="../README.md">简体中文</a> ·
|
||||
<a href="./README_en.md">English</a> ·
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## پیشنمایش
|
||||
|
||||
| تاریک | روشن |
|
||||
| ----------------------------------- | ------------------------------------- |
|
||||
|  |  |
|
||||
|
||||
## نصب
|
||||
|
||||
برای دانلود فایل نصبی متناسب با پلتفرم خود، به [صفحه انتشار](https://github.com/clash-verge-rev/clash-verge-rev/releases) مراجعه کنید.<br> ما بستههایی برای ویندوز (x64/x86)، لینوکس (x64/arm64) و macOS 10.15+ (اینتل/اپل) ارائه میدهیم.
|
||||
|
||||
#### انتخاب کانال انتشار
|
||||
|
||||
| Channel | توضیحات | Link |
|
||||
| :---------- | :------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- |
|
||||
| Stable | ساخت رسمی با قابلیت اطمینان بالا، ایدهآل برای استفاده روزانه. | [Release](https://github.com/clash-verge-rev/clash-verge-rev/releases) |
|
||||
| Alpha (EOL) | نسخههای قدیمی (Legacy builds) برای اعتبارسنجی خط لوله انتشار (publish pipeline) استفاده میشوند. | [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) |
|
||||
| AutoBuild | نسخههای آزمایشی برای آزمایش و دریافت بازخورد. منتظر تغییرات آزمایشی باشید. | [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) |
|
||||
|
||||
#### راهنماهای نصب و سوالات متداول
|
||||
|
||||
برای مراحل نصب، عیبیابی و سوالات متداول، [مستندات پروژه](https://clash-verge-rev.github.io/) را مطالعه کنید.
|
||||
|
||||
---
|
||||
|
||||
### کانال تلگرام
|
||||
|
||||
برای اطلاع از آخرین اخبار به [@clash_verge_rev](https://t.me/clash_verge_re) بپیوندید.
|
||||
|
||||
## تبلیغات
|
||||
|
||||
#### [Doggygo VPN — شتابدهنده جهانی عملکردگرا](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
- سرویس شبکه برون مرزی با عملکرد بالا به همراه دورههای آزمایشی رایگان، طرحهای تخفیفدار، امکان باز کردن قفل استریم و پشتیبانی درجه یک از پروتکل هیستریا.
|
||||
- از طریق لینک اختصاصی Clash Verge ثبت نام کنید تا یک دوره آزمایشی ۳ روزه با ۱ گیگابایت ترافیک در روز دریافت کنید: [ثبت نام](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
- کوپن تخفیف ۲۰٪ ویژه کاربران Clash Verge: `verge20` (محدود به ۵۰۰ بار استفاده)
|
||||
- بسته تخفیفدار از ۱۵.۸ ین در ماه برای ۱۶۰ گیگابایت، به علاوه ۲۰٪ تخفیف اضافی برای صورتحساب سالانه
|
||||
- توسط یک تیم خارجی با خدمات قابل اعتماد و تا 50٪ سهم درآمد اداره میشود
|
||||
- کلاسترهای متعادل بار با مسیرهای اختصاصی پرسرعت (سازگار با کلاینتهای قدیمی)، تأخیر فوقالعاده کم، پخش روان 4K
|
||||
- اولین ارائهدهنده جهانی که از پروتکل «Hysteria2» پشتیبانی میکند - کاملاً مناسب برای کلاینت Clash Verge
|
||||
- پشتیبانی از سرویسهای استریم و دسترسی به ChatGPT
|
||||
- وبسایت رسمی: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||
|
||||
#### حامی زیرساخت ساخت — [سرورهای اختصاصی YXVM](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
بیلدها و نسخههای ما روی سرورهای اختصاصی YXVM اجرا میشوند که منابع ممتاز، عملکرد قوی و شبکه پرسرعت را ارائه میدهند. اگر دانلودها سریع و استفاده از آن سریع به نظر میرسد، به لطف سختافزار قوی است.
|
||||
🧩 نکات برجسته سرورهای اختصاصی YXVM:
|
||||
|
||||
- 🌎 مسیرهای جهانی بهینه شده برای دانلودهای بسیار سریعتر
|
||||
- 🔧 منابع فیزیکی به جای ظرفیت VPS مشترک برای حداکثر کارایی
|
||||
- 🧠 عالی برای بارهای کاری پروکسی، میزبانی سرویسهای وب/CDN، خطوط لوله CI/CD یا هرگونه کار با بار بالا
|
||||
- 💡 آماده استفاده فوری با گزینههای متعدد مرکز داده، از جمله CN2 و IEPL
|
||||
- 📦 پیکربندی مورد استفاده در این پروژه در حال فروش است - میتوانید همان تنظیمات را تهیه کنید.
|
||||
- 🎯 آیا محیط ساخت مشابهی میخواهید؟ [همین امروز یک سرور YXVM سفارش دهید](https://yxvm.com/aff.php?aff=827)
|
||||
|
||||
## ویژگیها
|
||||
|
||||
- ساخته شده بر اساس Rust با کارایی بالا و فریمورک Tauri 2
|
||||
- با هسته جاسازیشده [Clash.Meta (mihomo)](https://github.com/MetaCubeX/mihomo) ارائه میشود و از تغییر به کانال «آلفا» پشتیبانی میکند.
|
||||
- رابط کاربری تمیز و مرتب با کنترلهای رنگ تم، آیکونهای گروه/سینی پروکسی و `تزریق CSS`
|
||||
- مدیریت پروفایل پیشرفته (ادغام و کمککنندههای اسکریپت) با نکات مربوط به سینتکس پیکربندی
|
||||
- کنترلهای پروکسی سیستم، حالت محافظت و پشتیبانی از `TUN` (آداپتور شبکه مجازی)
|
||||
- ویرایشگرهای بصری برای گرهها و قوانین
|
||||
- پشتیبانگیری و همگامسازی مبتنی بر WebDAV برای تنظیمات
|
||||
|
||||
### سوالات متداول
|
||||
|
||||
برای راهنماییهای مربوط به هر پلتفرم، به [صفحه سوالات متداول](https://clash-verge-rev.github.io/faq/windows.html) مراجعه کنید.
|
||||
|
||||
### اهدا
|
||||
|
||||
[پشتیبانی از توسعه Clash Verge Rev](https://github.com/sponsors/clash-verge-rev)
|
||||
|
||||
## توسعه
|
||||
|
||||
برای دستورالعملهای دقیق مشارکت، به [CONTRIBUTING.md](../CONTRIBUTING.md) مراجعه کنید.
|
||||
|
||||
پس از نصب تمام پیشنیازهای **Tauri**، پوسته توسعه را با دستور زیر اجرا کنید:
|
||||
|
||||
```shell
|
||||
pnpm i
|
||||
pnpm run prebuild
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## مشارکتها
|
||||
|
||||
مشکلات و درخواستهای pull مورد استقبال قرار میگیرند!
|
||||
|
||||
## تقدیر و تشکر
|
||||
|
||||
Clash Verge Rev بر اساس این پروژهها ساخته شده یا از آنها الهام گرفته است:
|
||||
|
||||
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): یک رابط کاربری گرافیکی Clash مبتنی بر Tauri برای ویندوز، macOS و لینوکس..
|
||||
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): ساخت برنامههای دسکتاپ کوچکتر، سریعتر و امنتر با رابط کاربری وب.
|
||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash): یک تونل مبتنی بر قانون که با زبان Go نوشته شده است.
|
||||
- [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo): یک تونل مبتنی بر قانون که با زبان Go نوشته شده است.
|
||||
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): رابط کاربری گرافیکی Clash برای ویندوز و macOS.
|
||||
- [vitejs/vite](https://github.com/vitejs/vite): ابزارهای فرانتاند نسل بعدی با DX فوقالعاده سریع.
|
||||
|
||||
## مجوز
|
||||
|
||||
مجوز GPL-3.0. برای جزئیات بیشتر به [فایل مجوز](../LICENSE) مراجعه کنید.
|
||||
+2
-1
@@ -16,7 +16,8 @@
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## プレビュー
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
|
||||
## 미리보기
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ Clash Meta GUI базируется на <a href="https://github.com/tauri-apps/
|
||||
<a href="./README_es.md">Español</a> ·
|
||||
<a href="./README_ru.md">Русский</a> ·
|
||||
<a href="./README_ja.md">日本語</a> ·
|
||||
<a href="./README_ko.md">한국어</a>
|
||||
<a href="./README_ko.md">한국어</a> ·
|
||||
<a href="./README_fa.md">فارسی</a>
|
||||
</p>
|
||||
## Предпросмотр
|
||||
|
||||
|
||||
+32
-22
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.4.4-rc",
|
||||
"version": "2.4.4",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"prepare": "husky || true",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@mui/lab": "7.0.0-beta.17",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tanstack/react-virtual": "^3.13.13",
|
||||
"@tauri-apps/api": "2.9.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
@@ -57,32 +57,32 @@
|
||||
"axios": "^1.13.2",
|
||||
"dayjs": "1.11.19",
|
||||
"foxact": "^0.2.49",
|
||||
"i18next": "^25.7.1",
|
||||
"i18next": "^25.7.3",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash-es": "^4.17.22",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-yaml": "^5.4.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-hook-form": "^7.67.0",
|
||||
"react-i18next": "16.4.0",
|
||||
"react-hook-form": "^7.68.0",
|
||||
"react-i18next": "16.5.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-router": "^7.10.0",
|
||||
"react-virtuoso": "^4.16.1",
|
||||
"swr": "^2.3.7",
|
||||
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#main",
|
||||
"react-router": "^7.11.0",
|
||||
"react-virtuoso": "^4.17.0",
|
||||
"swr": "^2.3.8",
|
||||
"tauri-plugin-mihomo-api": "github:clash-verge-rev/tauri-plugin-mihomo#main",
|
||||
"types-pac": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^6.0.1",
|
||||
"@eslint-react/eslint-plugin": "^2.3.12",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@tauri-apps/cli": "2.9.5",
|
||||
"@eslint-react/eslint-plugin": "^2.3.13",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tauri-apps/cli": "2.9.6",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/node": "^24.10.4",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
@@ -91,13 +91,13 @@
|
||||
"cli-color": "^2.0.4",
|
||||
"commander": "^14.0.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import-x": "^4.16.1",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"glob": "^13.0.0",
|
||||
"globals": "^16.5.0",
|
||||
@@ -107,12 +107,12 @@
|
||||
"lint-staged": "^16.2.7",
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^3.7.4",
|
||||
"sass": "^1.94.2",
|
||||
"sass": "^1.97.0",
|
||||
"tar": "^7.5.2",
|
||||
"terser": "^5.44.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"vite": "^7.2.6",
|
||||
"typescript-eslint": "^8.50.0",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-svgr": "^4.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -125,5 +125,15 @@
|
||||
]
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.22.0"
|
||||
"packageManager": "pnpm@10.26.1",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@swc/core",
|
||||
"core-js",
|
||||
"es5-ext",
|
||||
"esbuild",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1420
-1396
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- "@parcel/watcher"
|
||||
- "@swc/core"
|
||||
- core-js
|
||||
- es5-ext
|
||||
- esbuild
|
||||
- unrs-resolver
|
||||
+1
-1
@@ -45,6 +45,6 @@
|
||||
groupName: "github actions",
|
||||
},
|
||||
],
|
||||
postUpdateOptions: ["pnpmDedupe", "updateCargoLock"],
|
||||
postUpdateOptions: ["pnpmDedupe"],
|
||||
ignoreDeps: ["criterion"],
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ const IGNORED_KEY_PREFIXES = new Set([
|
||||
|
||||
const NOTICE_METHOD_NAMES = new Set(["success", "error", "info", "warning"]);
|
||||
const NOTICE_SERVICE_IDENTIFIERS = new Set([
|
||||
"@/services/noticeService",
|
||||
"./noticeService",
|
||||
"../services/noticeService",
|
||||
"@/services/notice-service",
|
||||
"./notice-service",
|
||||
"../services/notice-service",
|
||||
]);
|
||||
|
||||
const WHITELIST_KEYS = new Set([
|
||||
|
||||
+1
-12
@@ -576,7 +576,7 @@ const resolveServicePermission = async () => {
|
||||
};
|
||||
|
||||
// =======================
|
||||
// Other resource resolvers (service, mmdb, geosite, geoip, enableLoopback, sysproxy)
|
||||
// Other resource resolvers (service, mmdb, geosite, geoip, enableLoopback)
|
||||
// =======================
|
||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service-ipc/releases/download/${SIDECAR_HOST}`;
|
||||
const resolveService = () => {
|
||||
@@ -624,11 +624,6 @@ const resolveEnableLoopback = () =>
|
||||
file: "enableLoopback.exe",
|
||||
downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`,
|
||||
});
|
||||
const resolveWinSysproxy = () =>
|
||||
resolveResource({
|
||||
file: "sysproxy.exe",
|
||||
downloadURL: `https://github.com/clash-verge-rev/sysproxy/releases/download/${arch}/sysproxy.exe`,
|
||||
});
|
||||
|
||||
const resolveSetDnsScript = () =>
|
||||
resolveResource({
|
||||
@@ -676,12 +671,6 @@ const tasks = [
|
||||
retry: 5,
|
||||
unixOnly: platform === "linux" || platform === "darwin",
|
||||
},
|
||||
{
|
||||
name: "windows-sysproxy",
|
||||
func: resolveWinSysproxy,
|
||||
retry: 5,
|
||||
winOnly: true,
|
||||
},
|
||||
{
|
||||
name: "set_dns_script",
|
||||
func: resolveSetDnsScript,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.4.4-rc"
|
||||
version = "2.4.4"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "Tunglies", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
||||
default-run = "clash-verge"
|
||||
build = "build.rs"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
@@ -22,6 +22,7 @@ tauri-dev = ["clash-verge-logging/tauri-dev"]
|
||||
tokio-trace = ["console-subscriber"]
|
||||
clippy = ["tauri/test"]
|
||||
tracing = []
|
||||
tracing-full = []
|
||||
|
||||
[package.metadata.bundle]
|
||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||
@@ -93,7 +94,7 @@ tauri-plugin-devtools = { version = "2.0.1" }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo" }
|
||||
clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" }
|
||||
async-trait = "0.1.89"
|
||||
clash_verge_service_ipc = { version = "2.0.24", features = [
|
||||
clash_verge_service_ipc = { version = "2.0.26", features = [
|
||||
"client",
|
||||
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
@@ -25,8 +25,8 @@ notifications:
|
||||
title: 应用已隐藏
|
||||
body: Clash Verge 正在后台运行。
|
||||
service:
|
||||
adminInstallPrompt: 安装服务需要管理员权限
|
||||
adminUninstallPrompt: 写在服务需要管理员权限
|
||||
adminInstallPrompt: 安装 Clash Verge 服务需要管理员权限
|
||||
adminUninstallPrompt: 卸载 Clash Verge 服务需要管理员权限
|
||||
tray:
|
||||
dashboard: 仪表板
|
||||
ruleMode: 规则模式
|
||||
|
||||
@@ -99,7 +99,7 @@ pub async fn update_proxy_chain_config_in_runtime(proxy_chain_config: Option<ser
|
||||
runtime.edit_draft(|d| d.update_proxy_chain_config(proxy_chain_config));
|
||||
// 我们需要在 CoreManager 中验证并应用配置,这里不应该直接调用 runtime.apply()
|
||||
}
|
||||
logging_error!(Type::Core, CoreManager::global().update_config().await);
|
||||
logging_error!(Type::Core, CoreManager::global().apply_generate_confihg().await);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -287,8 +287,12 @@ singleton!(Hotkey, INSTANCE);
|
||||
|
||||
impl Hotkey {
|
||||
pub async fn init(&self, skip: bool) -> Result<()> {
|
||||
if skip {
|
||||
logging!(debug, Type::Hotkey, "skip register all hotkeys");
|
||||
return Ok(());
|
||||
}
|
||||
let verge = Config::verge().await;
|
||||
let enable_global_hotkey = !skip && verge.data_arc().enable_global_hotkey.unwrap_or(true);
|
||||
let enable_global_hotkey = verge.latest_arc().enable_global_hotkey.unwrap_or(true);
|
||||
|
||||
logging!(
|
||||
debug,
|
||||
@@ -298,7 +302,7 @@ impl Hotkey {
|
||||
);
|
||||
|
||||
// Extract hotkeys data before async operations
|
||||
let hotkeys = verge.data_arc().hotkeys.clone();
|
||||
let hotkeys = verge.latest_arc().hotkeys.clone();
|
||||
|
||||
if let Some(hotkeys) = hotkeys {
|
||||
logging!(debug, Type::Hotkey, "Has {} hotkeys need to register", hotkeys.len());
|
||||
|
||||
@@ -60,7 +60,10 @@ impl CoreManager {
|
||||
|
||||
async fn perform_config_update(&self) -> Result<(bool, String)> {
|
||||
Config::generate().await?;
|
||||
self.apply_generate_confihg().await
|
||||
}
|
||||
|
||||
pub async fn apply_generate_confihg(&self) -> Result<(bool, String)> {
|
||||
match CoreConfigValidator::global().validate_config().await {
|
||||
Ok((true, _)) => {
|
||||
let run_path = Config::generate_file(ConfigType::Run).await?;
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
mpsc,
|
||||
},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
time::Instant,
|
||||
};
|
||||
use tauri::{Emitter as _, WebviewWindow};
|
||||
|
||||
@@ -85,15 +85,17 @@ impl NotificationSystem {
|
||||
}
|
||||
|
||||
fn worker_loop(rx: mpsc::Receiver<FrontendEvent>) {
|
||||
let handle = Handle::global();
|
||||
loop {
|
||||
let handle = Handle::global();
|
||||
if handle.is_exiting() {
|
||||
break;
|
||||
}
|
||||
match rx.recv_timeout(Duration::from_millis(1_000)) {
|
||||
match rx.recv() {
|
||||
Ok(event) => Self::process_event(handle, event),
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => (),
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
||||
Err(e) => {
|
||||
logging!(error, Type::System, "Notification System will exit, recv error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
config::{Config, IClashTemp},
|
||||
core::tray,
|
||||
core::tray::Tray,
|
||||
utils::{dirs, init::service_writer_config},
|
||||
};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
@@ -469,10 +469,23 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
||||
|
||||
/// 检查服务是否正在运行
|
||||
pub async fn is_service_available() -> Result<()> {
|
||||
if let Err(e) = Path::metadata(clash_verge_service_ipc::IPC_PATH.as_ref()) {
|
||||
logging!(warn, Type::Service, "Some issue with service IPC Path: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
clash_verge_service_ipc::connect().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 等待一会,再检查服务是否正在运行
|
||||
/// TODO 使用 tokio select 之类机制并结合 timeout 实现更优雅的等待机制,期望等待文件出现,再尝试连接
|
||||
pub async fn wait_and_check_service_available(status: &mut ServiceManager) -> Result<()> {
|
||||
status.0 = ServiceStatus::Unavailable("Waiting for service to be available".into());
|
||||
clash_verge_service_ipc::connect().await?;
|
||||
status.0 = ServiceStatus::Ready;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_service_ipc_path_exists() -> bool {
|
||||
Path::new(clash_verge_service_ipc::IPC_PATH).exists()
|
||||
}
|
||||
@@ -484,8 +497,8 @@ impl ServiceManager {
|
||||
|
||||
pub const fn config() -> clash_verge_service_ipc::IpcConfig {
|
||||
clash_verge_service_ipc::IpcConfig {
|
||||
default_timeout: Duration::from_millis(30),
|
||||
retry_delay: Duration::from_millis(250),
|
||||
default_timeout: Duration::from_millis(100),
|
||||
retry_delay: Duration::from_millis(200),
|
||||
max_retries: 6,
|
||||
}
|
||||
}
|
||||
@@ -504,6 +517,7 @@ impl ServiceManager {
|
||||
|
||||
pub async fn refresh(&mut self) -> Result<()> {
|
||||
let status = self.check_service_comprehensive().await;
|
||||
self.0 = status.clone();
|
||||
logging_error!(Type::Service, self.handle_service_status(&status).await);
|
||||
Ok(())
|
||||
}
|
||||
@@ -532,28 +546,22 @@ impl ServiceManager {
|
||||
match status {
|
||||
ServiceStatus::Ready => {
|
||||
logging!(info, Type::Service, "服务就绪,直接启动");
|
||||
self.0 = ServiceStatus::Ready;
|
||||
}
|
||||
ServiceStatus::NeedsReinstall | ServiceStatus::ReinstallRequired => {
|
||||
logging!(info, Type::Service, "服务需要重装,执行重装流程");
|
||||
reinstall_service().await?;
|
||||
self.0 = ServiceStatus::Ready;
|
||||
wait_and_check_service_available(self).await?;
|
||||
}
|
||||
ServiceStatus::ForceReinstallRequired => {
|
||||
logging!(info, Type::Service, "服务需要强制重装,执行强制重装流程");
|
||||
force_reinstall_service().await?;
|
||||
self.0 = ServiceStatus::Ready;
|
||||
wait_and_check_service_available(self).await?;
|
||||
}
|
||||
ServiceStatus::InstallRequired => {
|
||||
logging!(info, Type::Service, "需要安装服务,执行安装流程");
|
||||
install_service().await?;
|
||||
// compatible with older service version, force reinstall if service is unavailable
|
||||
// wait for service server is running
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
if is_service_available().await.is_err() {
|
||||
logging!(info, Type::Service, "服务需要强制重装,执行强制重装流程");
|
||||
force_reinstall_service().await?;
|
||||
}
|
||||
self.0 = ServiceStatus::Ready;
|
||||
wait_and_check_service_available(self).await?;
|
||||
}
|
||||
ServiceStatus::UninstallRequired => {
|
||||
logging!(info, Type::Service, "服务需要卸载,执行卸载流程");
|
||||
@@ -566,7 +574,9 @@ impl ServiceManager {
|
||||
return Err(anyhow::anyhow!("服务不可用: {}", reason));
|
||||
}
|
||||
}
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
|
||||
// 防止服务安装成功后,内核未完全启动导致系统托盘无法获取代理节点信息
|
||||
Tray::global().update_menu().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+48
-116
@@ -23,7 +23,6 @@ use tauri_plugin_autostart::ManagerExt as _;
|
||||
pub struct Sysopt {
|
||||
update_sysproxy: AtomicBool,
|
||||
reset_sysproxy: AtomicBool,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
inner_proxy: Arc<RwLock<(Sysproxy, Autoproxy)>>,
|
||||
guard: Arc<RwLock<GuardMonitor>>,
|
||||
}
|
||||
@@ -33,7 +32,6 @@ impl Default for Sysopt {
|
||||
Self {
|
||||
update_sysproxy: AtomicBool::new(false),
|
||||
reset_sysproxy: AtomicBool::new(false),
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
inner_proxy: Arc::new(RwLock::new((Sysproxy::default(), Autoproxy::default()))),
|
||||
guard: Arc::new(RwLock::new(GuardMonitor::new(GuardType::None, Duration::from_secs(30)))),
|
||||
}
|
||||
@@ -69,35 +67,6 @@ async fn get_bypass() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Uses tokio Command with CREATE_NO_WINDOW flag to avoid DLL initialization issues during shutdown
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn execute_sysproxy_command(args: Vec<std::string::String>) -> Result<()> {
|
||||
use crate::utils::dirs;
|
||||
use anyhow::bail;
|
||||
#[allow(unused_imports)] // Required for .creation_flags() method
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
use tokio::process::Command;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
|
||||
|
||||
if !sysproxy_exe.exists() {
|
||||
bail!("sysproxy.exe not found");
|
||||
}
|
||||
|
||||
let output = Command::new(sysproxy_exe)
|
||||
.args(args)
|
||||
.creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("sysproxy exe run failed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
singleton!(Sysopt, SYSOPT);
|
||||
|
||||
impl Sysopt {
|
||||
@@ -112,12 +81,12 @@ impl Sysopt {
|
||||
pub async fn refresh_guard(&self) {
|
||||
logging!(info, Type::Core, "Refreshing system proxy guard...");
|
||||
let verge = Config::verge().await.latest_arc();
|
||||
if !verge.enable_system_proxy.unwrap_or(false) {
|
||||
if !verge.enable_system_proxy.unwrap_or_default() {
|
||||
logging!(info, Type::Core, "System proxy is disabled.");
|
||||
self.access_guard().write().stop();
|
||||
return;
|
||||
}
|
||||
if !verge.enable_proxy_guard.unwrap_or(false) {
|
||||
if !verge.enable_proxy_guard.unwrap_or_default() {
|
||||
logging!(info, Type::Core, "System proxy guard is disabled.");
|
||||
return;
|
||||
}
|
||||
@@ -169,94 +138,62 @@ impl Sysopt {
|
||||
};
|
||||
let pac_port = IVerge::get_singleton_port();
|
||||
|
||||
let (sys_enable, pac_enable, proxy_host) = {
|
||||
let (sys_enable, pac_enable, proxy_host, proxy_guard) = {
|
||||
(
|
||||
verge.enable_system_proxy.unwrap_or(false),
|
||||
verge.proxy_auto_config.unwrap_or(false),
|
||||
verge.enable_system_proxy.unwrap_or_default(),
|
||||
verge.proxy_auto_config.unwrap_or_default(),
|
||||
verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")),
|
||||
verge.enable_proxy_guard.unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
// 先 await, 避免持有锁导致的 Send 问题
|
||||
let bypass = get_bypass().await;
|
||||
// 先 await, 避免持有锁导致的 Send 问题
|
||||
let bypass = get_bypass().await;
|
||||
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
sys.host = proxy_host.clone().into();
|
||||
sys.port = port;
|
||||
sys.bypass = bypass.into();
|
||||
|
||||
auto.enable = false;
|
||||
auto.url = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
||||
|
||||
self.access_guard().write().set_guard_type(GuardType::None);
|
||||
|
||||
if !sys_enable && !pac_enable {
|
||||
// disable proxy
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if pac_enable {
|
||||
sys.enable = false;
|
||||
sys.host = proxy_host.clone().into();
|
||||
sys.port = port;
|
||||
sys.bypass = bypass.into();
|
||||
|
||||
auto.enable = false;
|
||||
auto.url = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
||||
|
||||
if !sys_enable {
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
self.access_guard()
|
||||
.write()
|
||||
.set_guard_type(GuardType::Sysproxy(sys.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if pac_enable {
|
||||
sys.enable = false;
|
||||
auto.enable = true;
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
auto.enable = true;
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
if proxy_guard {
|
||||
self.access_guard()
|
||||
.write()
|
||||
.set_guard_type(GuardType::Autoproxy(auto.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if sys_enable {
|
||||
auto.enable = false;
|
||||
sys.enable = true;
|
||||
auto.set_auto_proxy()?;
|
||||
sys.set_system_proxy()?;
|
||||
if sys_enable {
|
||||
auto.enable = false;
|
||||
sys.enable = true;
|
||||
auto.set_auto_proxy()?;
|
||||
sys.set_system_proxy()?;
|
||||
if proxy_guard {
|
||||
self.access_guard()
|
||||
.write()
|
||||
.set_guard_type(GuardType::Sysproxy(sys.clone()));
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if !sys_enable {
|
||||
self.access_guard().write().set_guard_type(GuardType::None);
|
||||
return self.reset_sysproxy().await;
|
||||
}
|
||||
|
||||
let (args, guard_type): (Vec<std::string::String>, GuardType) = if pac_enable {
|
||||
let address = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
||||
(
|
||||
vec!["pac".into(), address.clone()],
|
||||
GuardType::Autoproxy(Autoproxy {
|
||||
enable: true,
|
||||
url: address,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
let address = format!("{proxy_host}:{port}");
|
||||
let bypass = get_bypass().await;
|
||||
let bypass_for_guard = bypass.as_str().to_owned();
|
||||
(
|
||||
vec!["global".into(), address.clone(), bypass.into()],
|
||||
GuardType::Sysproxy(Sysproxy {
|
||||
enable: true,
|
||||
host: proxy_host.clone().into(),
|
||||
port,
|
||||
bypass: bypass_for_guard,
|
||||
}),
|
||||
)
|
||||
};
|
||||
|
||||
execute_sysproxy_command(args).await?;
|
||||
self.access_guard().write().set_guard_type(guard_type);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -274,20 +211,15 @@ impl Sysopt {
|
||||
self.reset_sysproxy.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
//直接关闭所有代理
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
sys.set_system_proxy()?;
|
||||
auto.enable = false;
|
||||
auto.set_auto_proxy()?;
|
||||
}
|
||||
// close proxy guard
|
||||
self.access_guard().write().set_guard_type(GuardType::None);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
execute_sysproxy_command(vec!["set".into(), "1".into()]).await?;
|
||||
}
|
||||
// 直接关闭所有代理
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
sys.set_system_proxy()?;
|
||||
auto.enable = false;
|
||||
auto.set_auto_proxy()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -142,25 +142,6 @@ impl Timer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 每 3 秒更新系统托盘菜单,总共执行 3 次
|
||||
pub fn add_update_tray_menu_task(&self) -> Result<()> {
|
||||
let tid = self.timer_count.fetch_add(1, Ordering::SeqCst);
|
||||
let task = TaskBuilder::default()
|
||||
.set_task_id(tid)
|
||||
.set_maximum_parallel_runnable_num(1)
|
||||
.set_frequency_count_down_by_seconds(3, 3)
|
||||
.spawn_async_routine(|| async move {
|
||||
logging!(debug, Type::Timer, "Updating tray menu");
|
||||
crate::core::tray::Tray::global().update_menu().await
|
||||
})
|
||||
.context("failed to create update tray menu timer task")?;
|
||||
self.delay_timer
|
||||
.write()
|
||||
.add_task(task)
|
||||
.context("failed to add update tray menu timer task")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Refresh timer tasks with better error handling
|
||||
pub async fn refresh(&self) -> Result<()> {
|
||||
// Generate diff outside of lock to minimize lock contention
|
||||
|
||||
@@ -198,8 +198,6 @@ impl Tray {
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: 初始化时,暂时使用此方法更新系统托盘菜单,有效避免代理节点菜单空白
|
||||
crate::core::timer::Timer::global().add_update_tray_menu_task()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -482,12 +480,16 @@ impl Tray {
|
||||
let show_menu_on_left_click = {
|
||||
// TODO 优化这里 复用 verge
|
||||
let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
|
||||
let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
|
||||
tray_event.as_str() == "tray_menu"
|
||||
tray_event.is_some_and(|v| v == "tray_menu")
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let mut builder = TrayIconBuilder::with_id("main").icon(icon).icon_as_template(false);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let is_monochrome = verge.tray_icon.clone().is_none_or(|v| v == "monochrome");
|
||||
builder = builder.icon_as_template(is_monochrome);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
{
|
||||
@@ -505,16 +507,16 @@ impl Tray {
|
||||
..
|
||||
} = event
|
||||
{
|
||||
// 添加防抖检查,防止快速连击
|
||||
if !should_handle_tray_click() {
|
||||
logging!(info, Type::Tray, "click tray icon too fast, ignore");
|
||||
return;
|
||||
}
|
||||
AsyncHandler::spawn(|| async move {
|
||||
let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
|
||||
let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
|
||||
logging!(debug, Type::Tray, "tray event: {tray_event:?}");
|
||||
|
||||
// 添加防抖检查,防止快速连击
|
||||
if !should_handle_tray_click() {
|
||||
return;
|
||||
}
|
||||
|
||||
match tray_event.as_str() {
|
||||
"system_proxy" => feat::toggle_system_proxy().await,
|
||||
"tun_mode" => feat::toggle_tun_mode(None).await,
|
||||
@@ -523,7 +525,9 @@ impl Tray {
|
||||
WindowManager::show_main_window().await;
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
logging!(warn, Type::Tray, "invalid tray event: {}", tray_event);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -722,7 +726,13 @@ async fn create_tray_menu(
|
||||
|
||||
i18n::sync_locale().await;
|
||||
|
||||
let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await.ok();
|
||||
// TODO: should update tray menu again when it was timeout error
|
||||
let proxy_nodes_data = tokio::time::timeout(
|
||||
Duration::from_millis(1000),
|
||||
handle::Handle::mihomo().await.get_proxies(),
|
||||
)
|
||||
.await
|
||||
.map_or(None, |res| res.ok());
|
||||
|
||||
let runtime_proxy_groups_order = cmd::get_runtime_config()
|
||||
.await
|
||||
@@ -967,7 +977,7 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
AsyncHandler::spawn(|| async move {
|
||||
match event.id.as_ref() {
|
||||
mode @ (MenuIds::RULE_MODE | MenuIds::GLOBAL_MODE | MenuIds::DIRECT_MODE) => {
|
||||
// Removing the the "tray_" preffix and "_mode" suffix
|
||||
// Removing the the "tray_" prefix and "_mode" suffix
|
||||
let mode = &mode[5..mode.len() - 5];
|
||||
logging!(info, Type::ProxyMode, "Switch Proxy Mode To: {}", mode);
|
||||
feat::change_clash_mode(mode.into()).await;
|
||||
|
||||
@@ -111,7 +111,7 @@ async fn perform_profile_update(
|
||||
let profile_name = profiles_arc
|
||||
.get_name_by_uid(uid)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| String::from("UnKown Profile"));
|
||||
.unwrap_or_else(|| String::from("UnKnown Profile"));
|
||||
|
||||
let mut last_err;
|
||||
|
||||
|
||||
@@ -45,93 +45,25 @@ pub async fn clean_async() -> bool {
|
||||
|
||||
// 重置系统代理
|
||||
let proxy_task = tokio::task::spawn(async {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use sysproxy::{Autoproxy, Sysproxy};
|
||||
use winapi::um::winuser::{GetSystemMetrics, SM_SHUTTINGDOWN};
|
||||
|
||||
// 检查系统代理是否开启
|
||||
let sys_proxy_enabled = Config::verge().await.data_arc().enable_system_proxy.unwrap_or(false);
|
||||
|
||||
if !sys_proxy_enabled {
|
||||
logging!(info, Type::Window, "系统代理未启用,跳过重置");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否正在关机
|
||||
let is_shutting_down = unsafe { GetSystemMetrics(SM_SHUTTINGDOWN) != 0 };
|
||||
|
||||
if is_shutting_down {
|
||||
// sysproxy-rs 操作注册表(避免.exe的dll错误)
|
||||
logging!(info, Type::Window, "检测到正在关机,syspro-rs操作注册表关闭系统代理");
|
||||
|
||||
match Sysproxy::get_system_proxy() {
|
||||
Ok(mut sysproxy) => {
|
||||
sysproxy.enable = false;
|
||||
if let Err(e) = sysproxy.set_system_proxy() {
|
||||
logging!(warn, Type::Window, "Warning: 关机时关闭系统代理失败: {e}");
|
||||
} else {
|
||||
logging!(info, Type::Window, "系统代理已关闭(通过注册表)");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(warn, Type::Window, "Warning: 关机时获取代理设置失败: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭自动代理配置
|
||||
if let Ok(mut autoproxy) = Autoproxy::get_auto_proxy() {
|
||||
autoproxy.enable = false;
|
||||
let _ = autoproxy.set_auto_proxy();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 正常退出:使用 sysproxy.exe 重置代理
|
||||
logging!(info, Type::Window, "sysproxy.exe重置系统代理");
|
||||
|
||||
match timeout(Duration::from_secs(2), sysopt::Sysopt::global().reset_sysproxy()).await {
|
||||
Ok(Ok(_)) => {
|
||||
logging!(info, Type::Window, "系统代理已重置");
|
||||
true
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging!(warn, Type::Window, "Warning: 重置系统代理失败: {e}");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(warn, Type::Window, "Warning: 重置系统代理超时,继续退出流程");
|
||||
true
|
||||
}
|
||||
}
|
||||
let sys_proxy_enabled = Config::verge().await.data_arc().enable_system_proxy.unwrap_or(false);
|
||||
if !sys_proxy_enabled {
|
||||
logging!(info, Type::Window, "系统代理未启用,跳过重置");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 非 Windows 平台:正常重置代理
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let sys_proxy_enabled = Config::verge().await.data_arc().enable_system_proxy.unwrap_or(false);
|
||||
|
||||
if !sys_proxy_enabled {
|
||||
logging!(info, Type::Window, "系统代理未启用,跳过重置");
|
||||
return true;
|
||||
logging!(info, Type::Window, "开始重置系统代理...");
|
||||
match timeout(Duration::from_millis(1500), sysopt::Sysopt::global().reset_sysproxy()).await {
|
||||
Ok(Ok(_)) => {
|
||||
logging!(info, Type::Window, "系统代理已重置");
|
||||
true
|
||||
}
|
||||
|
||||
logging!(info, Type::Window, "开始重置系统代理...");
|
||||
|
||||
match timeout(Duration::from_millis(1500), sysopt::Sysopt::global().reset_sysproxy()).await {
|
||||
Ok(Ok(_)) => {
|
||||
logging!(info, Type::Window, "系统代理已重置");
|
||||
true
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging!(warn, Type::Window, "Warning: 重置系统代理失败: {e}");
|
||||
true
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(warn, Type::Window, "Warning: 重置系统代理超时,继续退出");
|
||||
true
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging!(warn, Type::Window, "Warning: 重置系统代理失败: {e}");
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(warn, Type::Window, "Warning: 重置系统代理超时,继续退出");
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -183,7 +115,7 @@ pub async fn clean_async() -> bool {
|
||||
Type::Window,
|
||||
"Warning: 停止core超时(可能系统正在关机),继续退出"
|
||||
);
|
||||
true
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+6
-10
@@ -10,10 +10,7 @@ mod feat;
|
||||
mod module;
|
||||
mod process;
|
||||
pub mod utils;
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::utils::linux;
|
||||
use crate::utils::resolve::init_signal;
|
||||
use crate::{constants::files, utils::resolve::prioritize_initialization};
|
||||
use crate::constants::files;
|
||||
use crate::{
|
||||
core::handle,
|
||||
process::AsyncHandler,
|
||||
@@ -232,9 +229,6 @@ pub fn run() {
|
||||
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
linux::configure_environment();
|
||||
|
||||
let builder = app_init::setup_plugins(tauri::Builder::default())
|
||||
.setup(|app| {
|
||||
#[allow(clippy::expect_used)]
|
||||
@@ -242,7 +236,7 @@ pub fn run() {
|
||||
.set(app.app_handle().clone())
|
||||
.expect("failed to set global app handle");
|
||||
|
||||
let _handle = AsyncHandler::block_on(async { prioritize_initialization().await });
|
||||
let _handle = resolve::init_work_dir_and_logger();
|
||||
|
||||
logging!(info, Type::Setup, "开始应用初始化...");
|
||||
if let Err(e) = app_init::setup_autostart(app) {
|
||||
@@ -258,7 +252,7 @@ pub fn run() {
|
||||
resolve::resolve_setup_handle();
|
||||
resolve::resolve_setup_async();
|
||||
resolve::resolve_setup_sync();
|
||||
init_signal();
|
||||
resolve::init_signal();
|
||||
resolve::resolve_done();
|
||||
|
||||
logging!(info, Type::Setup, "初始化已启动");
|
||||
@@ -342,7 +336,9 @@ pub fn run() {
|
||||
.register_system_hotkey(SystemHotkey::CmdW)
|
||||
.await;
|
||||
}
|
||||
let _ = hotkey::Hotkey::global().init(true).await;
|
||||
if !is_enable_global_hotkey {
|
||||
let _ = hotkey::Hotkey::global().init(false).await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use chrono::{Local, TimeZone as _};
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
#[cfg(all(not(feature = "tauri-dev"), not(feature = "tracing-full")))]
|
||||
use clash_verge_logging::NoModuleFilter;
|
||||
use clash_verge_logging::Type;
|
||||
use clash_verge_service_ipc::WriterConfig;
|
||||
@@ -65,7 +65,7 @@ pub async fn init_logger() -> Result<LoggerHandle> {
|
||||
},
|
||||
Cleanup::KeepLogFiles(log_max_count),
|
||||
);
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
#[cfg(all(not(feature = "tracing"), not(feature = "tracing-full")))]
|
||||
let logger = logger.filter(Box::new(NoModuleFilter(&[
|
||||
"wry",
|
||||
"tauri",
|
||||
|
||||
@@ -1,464 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DRM_PATH: &str = "/sys/class/drm";
|
||||
const INTEL_VENDOR_ID: &str = "0x8086";
|
||||
const NVIDIA_VENDOR_ID: &str = "0x10de";
|
||||
const NVIDIA_VERSION_PATH: &str = "/proc/driver/nvidia/version";
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct IntelGpuDetection {
|
||||
has_intel: bool,
|
||||
intel_is_primary: bool,
|
||||
inconclusive: bool,
|
||||
}
|
||||
|
||||
impl IntelGpuDetection {
|
||||
const fn should_disable_dmabuf(&self) -> bool {
|
||||
self.intel_is_primary || self.inconclusive
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct NvidiaGpuDetection {
|
||||
has_nvidia: bool,
|
||||
nvidia_is_primary: bool,
|
||||
missing_boot_vga: bool,
|
||||
open_kernel_module: bool,
|
||||
driver_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum NvidiaDmabufDisableReason {
|
||||
PrimaryOpenKernelModule,
|
||||
MissingBootVga,
|
||||
PreferNativeWayland,
|
||||
}
|
||||
|
||||
impl NvidiaGpuDetection {
|
||||
const fn disable_reason(&self, session: &SessionEnv) -> Option<NvidiaDmabufDisableReason> {
|
||||
if !session.is_wayland {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.has_nvidia {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.open_kernel_module {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.nvidia_is_primary {
|
||||
return Some(NvidiaDmabufDisableReason::PrimaryOpenKernelModule);
|
||||
}
|
||||
|
||||
if self.missing_boot_vga {
|
||||
return Some(NvidiaDmabufDisableReason::MissingBootVga);
|
||||
}
|
||||
|
||||
if session.prefer_native_wayland {
|
||||
return Some(NvidiaDmabufDisableReason::PreferNativeWayland);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SessionEnv {
|
||||
is_kde_plasma: bool,
|
||||
is_wayland: bool,
|
||||
prefer_native_wayland: bool,
|
||||
compositor_label: String,
|
||||
}
|
||||
|
||||
impl SessionEnv {
|
||||
fn gather() -> Self {
|
||||
let desktop_env = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().to_uppercase();
|
||||
let session_desktop = env::var("XDG_SESSION_DESKTOP").unwrap_or_default().to_uppercase();
|
||||
let desktop_session = env::var("DESKTOP_SESSION").unwrap_or_default().to_uppercase();
|
||||
|
||||
let is_kde_plasma = desktop_env.contains("KDE")
|
||||
|| session_desktop.contains("KDE")
|
||||
|| desktop_session.contains("KDE")
|
||||
|| desktop_env.contains("PLASMA")
|
||||
|| session_desktop.contains("PLASMA")
|
||||
|| desktop_session.contains("PLASMA");
|
||||
let is_hyprland =
|
||||
desktop_env.contains("HYPR") || session_desktop.contains("HYPR") || desktop_session.contains("HYPR");
|
||||
let is_wayland = env::var("XDG_SESSION_TYPE")
|
||||
.map(|value| value.eq_ignore_ascii_case("wayland"))
|
||||
.unwrap_or(false)
|
||||
|| env::var("WAYLAND_DISPLAY").is_ok();
|
||||
let prefer_native_wayland = is_wayland && (is_kde_plasma || is_hyprland);
|
||||
let compositor_label = if is_hyprland {
|
||||
String::from("Hyprland")
|
||||
} else if is_kde_plasma {
|
||||
String::from("KDE Plasma")
|
||||
} else {
|
||||
String::from("Wayland compositor")
|
||||
};
|
||||
|
||||
Self {
|
||||
is_kde_plasma,
|
||||
is_wayland,
|
||||
prefer_native_wayland,
|
||||
compositor_label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DmabufOverrides {
|
||||
user_preference: Option<bool>,
|
||||
dmabuf_override: Option<String>,
|
||||
}
|
||||
|
||||
impl DmabufOverrides {
|
||||
fn gather() -> Self {
|
||||
let user_preference =
|
||||
env::var("CLASH_VERGE_DMABUF")
|
||||
.ok()
|
||||
.and_then(|value| match value.trim().to_ascii_lowercase().as_str() {
|
||||
"1" | "true" | "enable" | "on" => Some(true),
|
||||
"0" | "false" | "disable" | "off" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
let dmabuf_override = env::var("WEBKIT_DISABLE_DMABUF_RENDERER").ok();
|
||||
|
||||
Self {
|
||||
user_preference,
|
||||
dmabuf_override,
|
||||
}
|
||||
}
|
||||
|
||||
const fn has_env_override(&self) -> bool {
|
||||
self.dmabuf_override.is_some()
|
||||
}
|
||||
|
||||
const fn should_override_env(&self, decision: &DmabufDecision) -> bool {
|
||||
if self.user_preference.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if decision.enable_dmabuf {
|
||||
return true;
|
||||
}
|
||||
|
||||
!self.has_env_override()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DmabufDecision {
|
||||
enable_dmabuf: bool,
|
||||
force_x11_backend: bool,
|
||||
warn: bool,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl DmabufDecision {
|
||||
fn resolve(
|
||||
session: &SessionEnv,
|
||||
overrides: &DmabufOverrides,
|
||||
intel_gpu: IntelGpuDetection,
|
||||
nvidia_gpu: &NvidiaGpuDetection,
|
||||
) -> Self {
|
||||
let mut decision = Self {
|
||||
enable_dmabuf: true,
|
||||
force_x11_backend: false,
|
||||
warn: false,
|
||||
message: None,
|
||||
};
|
||||
|
||||
match overrides.user_preference {
|
||||
Some(true) => {
|
||||
decision.enable_dmabuf = true;
|
||||
decision.message = Some("CLASH_VERGE_DMABUF=1: 强制启用 WebKit DMABUF 渲染。".into());
|
||||
}
|
||||
Some(false) => {
|
||||
decision.enable_dmabuf = false;
|
||||
decision.message = Some("CLASH_VERGE_DMABUF=0: 强制禁用 WebKit DMABUF 渲染。".into());
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if overrides.has_env_override() {
|
||||
if overrides.dmabuf_override.as_deref() == Some("1") {
|
||||
decision.enable_dmabuf = false;
|
||||
decision.message =
|
||||
Some("检测到 WEBKIT_DISABLE_DMABUF_RENDERER=1,沿用用户的软件渲染配置。".into());
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
} else {
|
||||
decision.enable_dmabuf = true;
|
||||
let value = overrides.dmabuf_override.clone().unwrap_or_default();
|
||||
decision.message = Some(format!(
|
||||
"检测到 WEBKIT_DISABLE_DMABUF_RENDERER={},沿用用户配置。",
|
||||
value
|
||||
));
|
||||
}
|
||||
} else if let Some(reason) = nvidia_gpu.disable_reason(session) {
|
||||
decision.enable_dmabuf = false;
|
||||
decision.warn = true;
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
let summary = nvidia_gpu
|
||||
.driver_summary
|
||||
.as_deref()
|
||||
.and_then(|line| {
|
||||
extract_nvidia_driver_version(line)
|
||||
.map(|version| format!("NVIDIA Open Kernel Module {}", version))
|
||||
})
|
||||
.unwrap_or_else(|| String::from("NVIDIA Open Kernel Module"));
|
||||
let message = match reason {
|
||||
NvidiaDmabufDisableReason::PrimaryOpenKernelModule => {
|
||||
format!(
|
||||
"Wayland 会话检测到 {}:禁用 WebKit DMABUF 渲染以规避协议错误。",
|
||||
summary
|
||||
)
|
||||
}
|
||||
NvidiaDmabufDisableReason::MissingBootVga => {
|
||||
format!(
|
||||
"Wayland 会话检测到 {},但缺少 boot_vga 信息:预防性禁用 WebKit DMABUF。",
|
||||
summary
|
||||
)
|
||||
}
|
||||
NvidiaDmabufDisableReason::PreferNativeWayland => format!(
|
||||
"Wayland ({}) + {}:检测到 NVIDIA Open Kernel Module 在辅 GPU 上运行,预防性禁用 WebKit DMABUF。",
|
||||
session.compositor_label, summary
|
||||
),
|
||||
};
|
||||
decision.message = Some(message);
|
||||
} else if session.prefer_native_wayland && !intel_gpu.should_disable_dmabuf() {
|
||||
decision.enable_dmabuf = true;
|
||||
decision.message = Some(format!(
|
||||
"Wayland + {} detected: 使用原生 DMABUF 渲染。",
|
||||
session.compositor_label
|
||||
));
|
||||
} else {
|
||||
decision.enable_dmabuf = false;
|
||||
if session.is_wayland && !session.prefer_native_wayland {
|
||||
decision.force_x11_backend = true;
|
||||
}
|
||||
|
||||
if intel_gpu.should_disable_dmabuf() && session.is_wayland {
|
||||
decision.warn = true;
|
||||
if intel_gpu.inconclusive {
|
||||
decision.message = Some("Wayland 上检测到 Intel GPU,但缺少 boot_vga 信息:预防性禁用 WebKit DMABUF,若确认非主 GPU 可通过 CLASH_VERGE_DMABUF=1 覆盖。".into());
|
||||
} else {
|
||||
decision.message = Some(
|
||||
"Wayland 上检测到 Intel 主 GPU (0x8086):禁用 WebKit DMABUF 以避免帧缓冲失败。".into(),
|
||||
);
|
||||
}
|
||||
} else if session.is_wayland {
|
||||
decision.message = Some("Wayland 会话未匹配受支持的合成器:禁用 WebKit DMABUF 渲染。".into());
|
||||
} else {
|
||||
decision.message = Some("禁用 WebKit DMABUF 渲染以获得更稳定的输出。".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decision
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_intel_gpu() -> IntelGpuDetection {
|
||||
let Ok(entries) = fs::read_dir(DRM_PATH) else {
|
||||
return IntelGpuDetection::default();
|
||||
};
|
||||
|
||||
let mut detection = IntelGpuDetection::default();
|
||||
let mut seen_devices: HashSet<PathBuf> = HashSet::new();
|
||||
let mut missing_boot_vga = false;
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
|
||||
if !(name.starts_with("renderD") || name.starts_with("card")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device_path = entry.path().join("device");
|
||||
let device_key = fs::canonicalize(&device_path).unwrap_or(device_path);
|
||||
|
||||
if !seen_devices.insert(device_key.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor_path = device_key.join("vendor");
|
||||
let Ok(vendor) = fs::read_to_string(&vendor_path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !vendor.trim().eq_ignore_ascii_case(INTEL_VENDOR_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
detection.has_intel = true;
|
||||
|
||||
let boot_vga_path = device_key.join("boot_vga");
|
||||
match fs::read_to_string(&boot_vga_path) {
|
||||
Ok(flag) => {
|
||||
if flag.trim() == "1" {
|
||||
detection.intel_is_primary = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
missing_boot_vga = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if detection.has_intel && !detection.intel_is_primary && missing_boot_vga {
|
||||
detection.inconclusive = true;
|
||||
}
|
||||
|
||||
detection
|
||||
}
|
||||
|
||||
fn detect_nvidia_gpu() -> NvidiaGpuDetection {
|
||||
let mut detection = NvidiaGpuDetection::default();
|
||||
let entries = match fs::read_dir(DRM_PATH) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"无法读取 DRM 设备目录 {}({}),尝试通过 NVIDIA 驱动摘要进行降级检测。",
|
||||
DRM_PATH,
|
||||
err
|
||||
);
|
||||
detection.driver_summary = read_nvidia_driver_summary();
|
||||
if let Some(summary) = detection.driver_summary.as_ref() {
|
||||
detection.open_kernel_module = summary_indicates_open_kernel_module(summary);
|
||||
detection.has_nvidia = true;
|
||||
detection.missing_boot_vga = true;
|
||||
} else {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"降级检测失败:未能读取 NVIDIA 驱动摘要,保留 WebKit DMABUF。"
|
||||
);
|
||||
}
|
||||
return detection;
|
||||
}
|
||||
};
|
||||
|
||||
let mut seen_devices: HashSet<PathBuf> = HashSet::new();
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
|
||||
if !(name.starts_with("renderD") || name.starts_with("card")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device_path = entry.path().join("device");
|
||||
let device_key = fs::canonicalize(&device_path).unwrap_or(device_path);
|
||||
|
||||
if !seen_devices.insert(device_key.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor_path = device_key.join("vendor");
|
||||
let Ok(vendor) = fs::read_to_string(&vendor_path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !vendor.trim().eq_ignore_ascii_case(NVIDIA_VENDOR_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
detection.has_nvidia = true;
|
||||
|
||||
let boot_vga_path = device_key.join("boot_vga");
|
||||
match fs::read_to_string(&boot_vga_path) {
|
||||
Ok(flag) => {
|
||||
if flag.trim() == "1" {
|
||||
detection.nvidia_is_primary = true;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
detection.missing_boot_vga = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if detection.has_nvidia {
|
||||
detection.driver_summary = read_nvidia_driver_summary();
|
||||
match detection.driver_summary.as_ref() {
|
||||
Some(summary) => {
|
||||
detection.open_kernel_module = summary_indicates_open_kernel_module(summary);
|
||||
}
|
||||
None => {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"检测到 NVIDIA 设备,但无法读取 {},默认视为未启用开源内核模块。",
|
||||
NVIDIA_VERSION_PATH
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detection
|
||||
}
|
||||
|
||||
fn read_nvidia_driver_summary() -> Option<String> {
|
||||
match fs::read_to_string(NVIDIA_VERSION_PATH) {
|
||||
Ok(content) => content
|
||||
.lines()
|
||||
.next()
|
||||
.map(|line| line.trim().to_string())
|
||||
.filter(|line| !line.is_empty()),
|
||||
Err(err) => {
|
||||
logging!(info, Type::Setup, "读取 {} 失败:{}", NVIDIA_VERSION_PATH, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn summary_indicates_open_kernel_module(summary: &str) -> bool {
|
||||
let normalized = summary.to_ascii_lowercase();
|
||||
const PATTERNS: [&str; 4] = [
|
||||
"open kernel module",
|
||||
"open kernel modules",
|
||||
"open gpu kernel module",
|
||||
"open gpu kernel modules",
|
||||
];
|
||||
|
||||
let is_open = PATTERNS.iter().any(|pattern| normalized.contains(pattern));
|
||||
|
||||
if !is_open && normalized.contains("open") {
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"检测到 NVIDIA 驱动摘要包含 open 关键字但未匹配已知开源模块格式:{}",
|
||||
summary
|
||||
);
|
||||
}
|
||||
|
||||
is_open
|
||||
}
|
||||
|
||||
fn extract_nvidia_driver_version(summary: &str) -> Option<&str> {
|
||||
summary
|
||||
.split_whitespace()
|
||||
.find(|token| token.chars().all(|c| c.is_ascii_digit() || c == '.'))
|
||||
}
|
||||
|
||||
pub fn ensure_mimeapps_entries(desktop_file: &str, schemes: &[&str]) -> Result<()> {
|
||||
let Some(path) = mimeapps_list_path() else {
|
||||
return Ok(());
|
||||
@@ -716,52 +261,3 @@ fn match_scheme<'a>(key: &str, schemes: &'a [&str]) -> Option<&'a str> {
|
||||
|
||||
schemes.iter().copied().find(|candidate| *candidate == key)
|
||||
}
|
||||
|
||||
pub fn configure_environment() {
|
||||
let session = SessionEnv::gather();
|
||||
let overrides = DmabufOverrides::gather();
|
||||
let intel_gpu = detect_intel_gpu();
|
||||
let nvidia_gpu = detect_nvidia_gpu();
|
||||
let decision = DmabufDecision::resolve(&session, &overrides, intel_gpu, &nvidia_gpu);
|
||||
|
||||
if overrides.should_override_env(&decision) {
|
||||
unsafe {
|
||||
if decision.enable_dmabuf {
|
||||
env::remove_var("WEBKIT_DISABLE_DMABUF_RENDERER");
|
||||
} else {
|
||||
env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = decision.message {
|
||||
if decision.warn {
|
||||
logging!(warn, Type::Setup, "{}", message);
|
||||
} else {
|
||||
logging!(info, Type::Setup, "{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
if decision.force_x11_backend {
|
||||
unsafe {
|
||||
env::set_var("GDK_BACKEND", "x11");
|
||||
env::remove_var("WAYLAND_DISPLAY");
|
||||
}
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"Wayland detected: Forcing X11 backend for WebKit stability."
|
||||
);
|
||||
}
|
||||
|
||||
if session.is_kde_plasma {
|
||||
unsafe {
|
||||
env::set_var("GTK_CSD", "0");
|
||||
}
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
"KDE/Plasma detected: Disabled GTK CSD for better titlebar stability."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,21 @@ pub mod window_script;
|
||||
|
||||
static RESOLVE_DONE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub async fn prioritize_initialization() -> Option<LoggerHandle> {
|
||||
init_work_config().await;
|
||||
init_resources().await;
|
||||
pub fn init_work_dir_and_logger() -> Option<LoggerHandle> {
|
||||
AsyncHandler::block_on(async {
|
||||
init_work_config().await;
|
||||
init_resources().await;
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
{
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
init::init_logger().await.ok()
|
||||
}
|
||||
#[cfg(feature = "tauri-dev")]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
{
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
init::init_logger().await.ok()
|
||||
}
|
||||
#[cfg(feature = "tauri-dev")]
|
||||
{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_setup_handle() {
|
||||
@@ -58,8 +60,7 @@ pub fn resolve_setup_async() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
logging!(info, Type::ClashVergeRev, "Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
futures::join!(init_work_config(), init_resources(), init_startup_script());
|
||||
|
||||
init_startup_script().await;
|
||||
init_verge_config().await;
|
||||
Config::verify_config_initialization().await;
|
||||
init_window().await;
|
||||
@@ -130,7 +131,9 @@ pub(super) async fn init_timer() {
|
||||
}
|
||||
|
||||
pub(super) async fn init_hotkey() {
|
||||
logging_error!(Type::Setup, Hotkey::global().init(false).await);
|
||||
// if hotkey is not use by global, skip init it
|
||||
let skip_register_hotkeys = !Config::verge().await.latest_arc().enable_global_hotkey.unwrap_or(true);
|
||||
logging_error!(Type::Setup, Hotkey::global().init(skip_register_hotkeys).await);
|
||||
}
|
||||
|
||||
pub(super) async fn init_auto_lightweight_boot() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.4.4-rc",
|
||||
"version": "2.4.4",
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"bundle": {
|
||||
"active": true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
subscribeNotices,
|
||||
hideNotice,
|
||||
getSnapshotNotices,
|
||||
} from "@/services/noticeService";
|
||||
} from "@/services/notice-service";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
|
||||
export const NoticeManager: React.FC = () => {
|
||||
|
||||
@@ -478,6 +478,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
overflow: "auto",
|
||||
WebkitOverflowScrolling: "touch",
|
||||
overscrollBehavior: "contain",
|
||||
borderRadius: 1,
|
||||
border: "none",
|
||||
"&::-webkit-scrollbar": {
|
||||
|
||||
@@ -111,6 +111,7 @@ export const CurrentProxyCard = () => {
|
||||
const { verge } = useVerge();
|
||||
const { current: currentProfile } = useProfiles();
|
||||
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false;
|
||||
const defaultLatencyTimeout = verge?.default_latency_timeout;
|
||||
const currentProfileId = currentProfile?.uid || null;
|
||||
|
||||
const getProfileStorageKey = useCallback(
|
||||
@@ -742,20 +743,38 @@ export const CurrentProxyCard = () => {
|
||||
|
||||
if (sortType === 1) {
|
||||
const refreshTick = delaySortRefresh;
|
||||
const effectiveTimeout =
|
||||
typeof defaultLatencyTimeout === "number" && defaultLatencyTimeout > 0
|
||||
? defaultLatencyTimeout
|
||||
: 10000;
|
||||
|
||||
const categorizeDelay = (delay: number): [number, number] => {
|
||||
if (!Number.isFinite(delay)) return [5, Number.MAX_SAFE_INTEGER];
|
||||
if (delay > 1e5) return [4, delay];
|
||||
if (delay === 0 || (delay >= effectiveTimeout && delay <= 1e5)) {
|
||||
return [3, delay || effectiveTimeout];
|
||||
}
|
||||
if (delay < 0) return [5, Number.MAX_SAFE_INTEGER];
|
||||
return [0, delay];
|
||||
};
|
||||
|
||||
list.sort((a, b) => {
|
||||
const recordA = state.proxyData.records[a.name];
|
||||
const recordB = state.proxyData.records[b.name];
|
||||
|
||||
if (!recordA) return 1;
|
||||
if (!recordB) return -1;
|
||||
const [ar, av] = recordA
|
||||
? categorizeDelay(
|
||||
delayManager.getDelayFix(recordA, state.selection.group),
|
||||
)
|
||||
: [6, Number.MAX_SAFE_INTEGER];
|
||||
const [br, bv] = recordB
|
||||
? categorizeDelay(
|
||||
delayManager.getDelayFix(recordB, state.selection.group),
|
||||
)
|
||||
: [6, Number.MAX_SAFE_INTEGER];
|
||||
|
||||
const ad = delayManager.getDelayFix(recordA, state.selection.group);
|
||||
const bd = delayManager.getDelayFix(recordB, state.selection.group);
|
||||
|
||||
if (ad === -1 || ad === -2) return 1;
|
||||
if (bd === -1 || bd === -2) return -1;
|
||||
|
||||
if (ad !== bd) return ad - bd;
|
||||
if (ar !== br) return ar - br;
|
||||
if (av !== bv) return av - bv;
|
||||
return refreshTick >= 0 ? a.name.localeCompare(b.name) : 0;
|
||||
});
|
||||
} else {
|
||||
@@ -800,6 +819,7 @@ export const CurrentProxyCard = () => {
|
||||
state.selection.group,
|
||||
sortType,
|
||||
delaySortRefresh,
|
||||
defaultLatencyTimeout,
|
||||
]);
|
||||
|
||||
// 获取排序图标
|
||||
|
||||
@@ -12,10 +12,7 @@ import {
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
useTrafficGraphDataEnhanced,
|
||||
type ITrafficDataPoint,
|
||||
} from "@/hooks/use-traffic-monitor";
|
||||
import { useTrafficGraphDataEnhanced } from "@/hooks/use-traffic-monitor";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useNavigate } from "react-router";
|
||||
|
||||
import { useRefreshAll } from "@/hooks/use-clash-data";
|
||||
import { openWebUrl, updateProfile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
import { EnhancedCard } from "./enhanced-card";
|
||||
|
||||
@@ -17,11 +17,11 @@ import {
|
||||
import { useState, useMemo, memo, FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
||||
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
|
||||
import { useSystemProxyState } from "@/hooks/use-system-proxy-state";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useServiceInstaller } from "@/hooks/use-service-installer";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { getSystemInfo } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
import { version as appVersion } from "@root/package.json";
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useTranslation } from "react-i18next";
|
||||
import pac from "types-pac/pac.d.ts?raw";
|
||||
|
||||
import { BaseLoadingOverlay } from "@/components/base";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import debounce from "@/utils/debounce";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
readProfileFile,
|
||||
saveProfileFile,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
saveProfileFile,
|
||||
getNextUpdateTime,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useLoadingCache, useSetLoadingCache } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { viewProfile, readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { LogViewer } from "./log-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
import { createProfile, patchProfile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { version } from "@root/package.json";
|
||||
|
||||
import { FileInput } from "./file-input";
|
||||
|
||||
@@ -42,7 +42,7 @@ import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
import { ProxyItem } from "@/components/profile/proxy-item";
|
||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import parseUri from "@/utils/uri-parser";
|
||||
|
||||
@@ -45,7 +45,7 @@ import { Virtuoso } from "react-virtuoso";
|
||||
import { Switch } from "@/components/base";
|
||||
import { RuleItem } from "@/components/profile/rule-item";
|
||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useProxiesData, useProxyProvidersData } from "@/hooks/use-clash-data";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
// 样式化组件 - 类型框
|
||||
|
||||
@@ -285,23 +285,37 @@ export const ProxyChain = ({
|
||||
|
||||
const handleConnect = useCallback(async () => {
|
||||
if (isConnected) {
|
||||
// 如果已连接,则断开连接
|
||||
setIsConnecting(true);
|
||||
try {
|
||||
// 清空链式代理配置
|
||||
await updateProxyChainConfigInRuntime(null);
|
||||
|
||||
// 切换到 DIRECT 模式断开代理连接
|
||||
// await updateProxyAndSync("GLOBAL", "DIRECT");
|
||||
const targetGroup =
|
||||
mode === "global"
|
||||
? "GLOBAL"
|
||||
: selectedGroup || localStorage.getItem("proxy-chain-group");
|
||||
|
||||
if (targetGroup) {
|
||||
try {
|
||||
await selectNodeForGroup(targetGroup, "DIRECT");
|
||||
} catch {
|
||||
if (proxyChain.length >= 1) {
|
||||
try {
|
||||
await selectNodeForGroup(targetGroup, proxyChain[0].name);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.removeItem("proxy-chain-group");
|
||||
localStorage.removeItem("proxy-chain-exit-node");
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
|
||||
// 关闭所有连接
|
||||
await closeAllConnections();
|
||||
await mutateProxies();
|
||||
|
||||
// 刷新代理信息以更新连接状态
|
||||
mutateProxies();
|
||||
|
||||
// 清空链式代理配置UI
|
||||
// onUpdateChain([]);
|
||||
onUpdateChain([]);
|
||||
} catch (error) {
|
||||
console.error("Failed to disconnect from proxy chain:", error);
|
||||
alert(t("proxies.page.chain.disconnectFailed") || "断开链式代理失败");
|
||||
@@ -348,7 +362,15 @@ export const ProxyChain = ({
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
}
|
||||
}, [proxyChain, isConnected, t, mutateProxies, mode, selectedGroup]);
|
||||
}, [
|
||||
proxyChain,
|
||||
isConnected,
|
||||
t,
|
||||
mutateProxies,
|
||||
mode,
|
||||
selectedGroup,
|
||||
onUpdateChain,
|
||||
]);
|
||||
|
||||
const proxyChainRef = useRef(proxyChain);
|
||||
const onUpdateChainRef = useRef(onUpdateChain);
|
||||
@@ -376,10 +398,11 @@ export const ProxyChain = ({
|
||||
type: proxy.type,
|
||||
delay: undefined,
|
||||
})) || [];
|
||||
onUpdateChain(chainItems);
|
||||
if (chainItems.length > 0) {
|
||||
onUpdateChain(chainItems);
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error("Failed to parse YAML:", parseError);
|
||||
onUpdateChain([]);
|
||||
}
|
||||
})
|
||||
.catch((importError) => {
|
||||
@@ -399,19 +422,16 @@ export const ProxyChain = ({
|
||||
type: proxy.type,
|
||||
delay: undefined,
|
||||
})) || [];
|
||||
onUpdateChain(chainItems);
|
||||
if (chainItems.length > 0) {
|
||||
onUpdateChain(chainItems);
|
||||
}
|
||||
} catch (jsonError) {
|
||||
console.error("Failed to parse as JSON either:", jsonError);
|
||||
onUpdateChain([]);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to process chain config data:", error);
|
||||
onUpdateChain([]);
|
||||
}
|
||||
} else if (chainConfigData === "") {
|
||||
// Empty string means no proxies available, show empty state
|
||||
onUpdateChain([]);
|
||||
}
|
||||
}, [chainConfigData, onUpdateChain]);
|
||||
|
||||
@@ -481,6 +501,9 @@ export const ProxyChain = ({
|
||||
size="small"
|
||||
onClick={() => {
|
||||
updateProxyChainConfigInRuntime(null);
|
||||
localStorage.removeItem("proxy-chain-group");
|
||||
localStorage.removeItem("proxy-chain-exit-node");
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
onUpdateChain([]);
|
||||
}}
|
||||
sx={{
|
||||
|
||||
@@ -27,8 +27,8 @@ import { ScrollTopButton } from "../layout/scroll-top-button";
|
||||
|
||||
import { ProxyChain } from "./proxy-chain";
|
||||
import {
|
||||
ProxyGroupNavigator,
|
||||
DEFAULT_HOVER_DELAY,
|
||||
ProxyGroupNavigator,
|
||||
} from "./proxy-group-navigator";
|
||||
import { ProxyRender } from "./proxy-render";
|
||||
import { useRenderList } from "./use-render-list";
|
||||
@@ -51,8 +51,26 @@ const VirtuosoFooter = () => <div style={{ height: "8px" }} />;
|
||||
export const ProxyGroups = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { mode, isChainMode = false, chainConfigData } = props;
|
||||
const [proxyChain, setProxyChain] = useState<ProxyChainItem[]>([]);
|
||||
const [proxyChain, setProxyChain] = useState<ProxyChainItem[]>(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem("proxy-chain-items");
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (proxyChain.length > 0) {
|
||||
localStorage.setItem("proxy-chain-items", JSON.stringify(proxyChain));
|
||||
} else {
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
}
|
||||
}, [proxyChain]);
|
||||
const [ruleMenuAnchor, setRuleMenuAnchor] = useState<null | HTMLElement>(
|
||||
null,
|
||||
);
|
||||
@@ -64,7 +82,13 @@ export const ProxyGroups = (props: Props) => {
|
||||
const { verge } = useVerge();
|
||||
const { proxies: proxiesData } = useProxiesData();
|
||||
const groups = proxiesData?.groups;
|
||||
const availableGroups = useMemo(() => groups ?? [], [groups]);
|
||||
const availableGroups = useMemo(() => {
|
||||
if (!groups) return [];
|
||||
// 在链式代理模式下,仅显示支持选择节点的 Selector 代理组
|
||||
return isChainMode
|
||||
? groups.filter((g: any) => g.type === "Selector")
|
||||
: groups;
|
||||
}, [groups, isChainMode]);
|
||||
|
||||
const defaultRuleGroup = useMemo(() => {
|
||||
if (isChainMode && mode === "rule" && availableGroups.length > 0) {
|
||||
@@ -225,10 +249,11 @@ export const ProxyGroups = (props: Props) => {
|
||||
setSelectedGroup(groupName);
|
||||
handleGroupMenuClose();
|
||||
|
||||
// 在链式代理模式的规则模式下,切换代理组时清空链式代理配置
|
||||
if (isChainMode && mode === "rule") {
|
||||
updateProxyChainConfigInRuntime(null);
|
||||
// 同时清空右侧链式代理配置
|
||||
localStorage.removeItem("proxy-chain-group");
|
||||
localStorage.removeItem("proxy-chain-exit-node");
|
||||
localStorage.removeItem("proxy-chain-items");
|
||||
setProxyChain([]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,11 +55,6 @@ export function useHeadStateNew() {
|
||||
const [state, dispatch] = useReducer(headStateReducer, {});
|
||||
|
||||
useEffect(() => {
|
||||
if (!current) {
|
||||
dispatch({ type: "reset" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(
|
||||
localStorage.getItem(HEAD_STATE_KEY)!,
|
||||
@@ -78,8 +73,6 @@ export function useHeadStateNew() {
|
||||
}, [current]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!current) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
const item = localStorage.getItem(HEAD_STATE_KEY);
|
||||
@@ -99,10 +92,9 @@ export function useHeadStateNew() {
|
||||
|
||||
const setHeadState = useCallback(
|
||||
(groupName: string, obj: Partial<HeadState>) => {
|
||||
if (!current) return;
|
||||
dispatch({ type: "update", groupName, patch: obj });
|
||||
},
|
||||
[current],
|
||||
[],
|
||||
);
|
||||
|
||||
return [state, setHeadState] as const;
|
||||
|
||||
@@ -25,7 +25,7 @@ import type {
|
||||
useRuleProvidersData,
|
||||
useRulesData,
|
||||
} from "@/hooks/use-clash-data";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
// 辅助组件 - 类型框
|
||||
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Switch } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
const MIN_INTERVAL_HOURS = 1;
|
||||
const MAX_INTERVAL_HOURS = 168;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { saveWebdavConfig, createWebdavBackup } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { isValidUrl } from "@/utils/helper";
|
||||
|
||||
interface BackupConfigViewerProps {
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
restoreLocalBackup,
|
||||
restoreWebDavBackup,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { createLocalBackup, createWebdavBackup } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { AutoBackupSettings } from "./auto-backup-settings";
|
||||
import { BackupHistoryViewer } from "./backup-history-viewer";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, BaseLoadingOverlay } from "@/components/base";
|
||||
import { listWebDavBackup } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { BackupConfigViewer } from "./backup-config-viewer";
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { closeAllConnections, upgradeCore } from "tauri-plugin-mihomo-api";
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { changeClashCore, restartCore } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
const VALID_CORE = [
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
const OS = getSystem();
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
// 定义开发环境的URL列表
|
||||
// 这些URL在开发模式下会被自动包含在允许的来源中
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { HotkeyInput } from "./hotkey-input";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { DEFAULT_HOVER_DELAY } from "@/components/proxy/proxy-group-navigator";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useWindowDecorations } from "@/hooks/use-window";
|
||||
import { copyIconFile, getAppDir } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { GuardState } from "./guard-state";
|
||||
|
||||
@@ -15,7 +15,7 @@ import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { entry_lightweight_mode } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -8,7 +8,7 @@ import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
getSystemProxy,
|
||||
patchVergeConfig,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { defaultDarkTheme, defaultTheme } from "@/pages/_theme";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
|
||||
const { ref } = props;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { enhanceProfiles } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { StackModeSwitch } from "./stack-mode-switch";
|
||||
@@ -33,7 +33,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
stack: "mixed",
|
||||
device: OS === "macos" ? "utun1024" : "Mihomo",
|
||||
autoRoute: true,
|
||||
autoRedirect: OS === "linux",
|
||||
autoRedirect: false,
|
||||
autoDetectInterface: true,
|
||||
dnsHijack: ["any:53"],
|
||||
strictRoute: false,
|
||||
@@ -44,7 +44,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
open: () => {
|
||||
setOpen(true);
|
||||
const nextAutoRoute = clash?.tun["auto-route"] ?? true;
|
||||
const rawAutoRedirect = clash?.tun["auto-redirect"] ?? true;
|
||||
const rawAutoRedirect = clash?.tun["auto-redirect"] ?? false;
|
||||
const computedAutoRedirect =
|
||||
OS === "linux" ? (nextAutoRoute ? rawAutoRedirect : false) : false;
|
||||
setValues({
|
||||
@@ -118,7 +118,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
"auto-route": true,
|
||||
...(OS === "linux"
|
||||
? {
|
||||
"auto-redirect": true,
|
||||
"auto-redirect": false,
|
||||
}
|
||||
: {}),
|
||||
"auto-detect-interface": true,
|
||||
@@ -130,7 +130,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
stack: "gvisor",
|
||||
device: OS === "macos" ? "utun1024" : "Mihomo",
|
||||
autoRoute: true,
|
||||
autoRedirect: OS === "linux" ? true : false,
|
||||
autoRedirect: false,
|
||||
autoDetectInterface: true,
|
||||
dnsHijack: ["any:53"],
|
||||
strictRoute: false,
|
||||
|
||||
@@ -11,7 +11,7 @@ import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { portableFlag } from "@/pages/_layout";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { useSetUpdateState, useUpdateState } from "@/services/states";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseDialog, BaseEmpty, DialogRef } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { openWebUrl } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { WebUIItem } from "./web-ui-item";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useClash } from "@/hooks/use-clash";
|
||||
import { useClashLog } from "@/hooks/use-clash-log";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { invoke_uwp_tool } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||
|
||||
@@ -6,10 +6,10 @@ import { mutate } from "swr";
|
||||
|
||||
import { DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
||||
import ProxyControlSwitches from "@/components/shared/proxy-control-switches";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { GuardState } from "./mods/guard-state";
|
||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
openDevTools,
|
||||
openLogsDir,
|
||||
} from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { checkUpdateSafe as checkUpdate } from "@/services/update";
|
||||
import { version } from "@root/package.json";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useVerge } from "@/hooks/use-verge";
|
||||
import { navItems } from "@/pages/_routers";
|
||||
import { copyClashEnv } from "@/services/cmds";
|
||||
import { supportedLanguages } from "@/services/i18n";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { BackupViewer } from "./mods/backup-viewer";
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import { useServiceUninstaller } from "@/hooks/use-service-uninstaller";
|
||||
import { useSystemProxyState } from "@/hooks/use-system-proxy-state";
|
||||
import { useSystemState } from "@/hooks/use-system-state";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
interface ProxySwitchProps {
|
||||
label?: string;
|
||||
@@ -12,7 +12,7 @@ import { BaseLoading } from "@/components/base";
|
||||
import { useListen } from "@/hooks/use-listen";
|
||||
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
|
||||
import delayManager from "@/services/delay";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
|
||||
import { TestBox } from "./test-box";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
interface Props {
|
||||
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void;
|
||||
|
||||
+13
-13
@@ -1,20 +1,20 @@
|
||||
import { event } from "@tauri-apps/api";
|
||||
import { listen, UnlistenFn, EventCallback } from "@tauri-apps/api/event";
|
||||
import { useRef } from "react";
|
||||
import { useCallback, useRef } from "react";
|
||||
|
||||
export const useListen = () => {
|
||||
const unlistenFns = useRef<UnlistenFn[]>([]);
|
||||
|
||||
const addListener = async <T>(
|
||||
eventName: string,
|
||||
handler: EventCallback<T>,
|
||||
) => {
|
||||
const unlisten = await listen(eventName, handler);
|
||||
unlistenFns.current.push(unlisten);
|
||||
return unlisten;
|
||||
};
|
||||
const addListener = useCallback(
|
||||
async <T>(eventName: string, handler: EventCallback<T>) => {
|
||||
const unlisten = await listen(eventName, handler);
|
||||
unlistenFns.current.push(unlisten);
|
||||
return unlisten;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const removeAllListeners = () => {
|
||||
const removeAllListeners = useCallback(() => {
|
||||
const errors: Error[] = [];
|
||||
|
||||
unlistenFns.current.forEach((unlisten) => {
|
||||
@@ -33,13 +33,13 @@ export const useListen = () => {
|
||||
}
|
||||
|
||||
unlistenFns.current.length = 0;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const setupCloseListener = async function () {
|
||||
const setupCloseListener = useCallback(async () => {
|
||||
await event.once("tauri://close-requested", async () => {
|
||||
removeAllListeners();
|
||||
});
|
||||
};
|
||||
}, [removeAllListeners]);
|
||||
|
||||
return {
|
||||
addListener,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { installService, restartCore } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { useSystemState } from "./use-system-state";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { restartCore, stopCore, uninstallService } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { useSystemState } from "./use-system-state";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { getRunningMode, isAdmin, isServiceAvailable } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
|
||||
import { useVerge } from "./use-verge";
|
||||
|
||||
|
||||
@@ -8,18 +8,9 @@ import {
|
||||
} from "react";
|
||||
import { Traffic } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import type {
|
||||
ISamplerStats,
|
||||
ITrafficDataPoint,
|
||||
ITrafficWorkerSnapshotMessage,
|
||||
TrafficWorkerRequestMessage,
|
||||
TrafficWorkerResponseMessage,
|
||||
} from "@/types/traffic-monitor";
|
||||
import { debugLog } from "@/utils/debug";
|
||||
import { TrafficDataSampler, formatTrafficName } from "@/utils/traffic-sampler";
|
||||
|
||||
export type { ITrafficDataPoint } from "@/types/traffic-monitor";
|
||||
|
||||
// 引用计数管理器
|
||||
class ReferenceCounter {
|
||||
private count = 0;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"reorderMode": "Menu reorder mode",
|
||||
"restoreDefaultOrder": "Restore default order",
|
||||
"unlock": "Unlock menu order",
|
||||
"lock": "Lock menu order"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user