Compare commits

...
70 Commits
Author SHA1 Message Date
Tunglies 787463a226 chore: bump version to 2.4.4 2025-12-19 16:25:24 +08:00
Slinetrac 8d5d72957b fix(home): correct delay sorting for Current Node card #5870
Closes #5870
2025-12-19 14:24:04 +08:00
Slinetrac 7d805cb83e chore(deps): bump npm deps 2025-12-19 10:32:40 +08:00
oomeow a1286ad057 fix: always occupies hotkey globally when app launch silently (#5866)
* fix: always occupies hotkey when app launch silently

* docs: update Changelog.md

* chore: update
2025-12-18 22:21:15 +08:00
oomeow 9713343323 refactor: use sysproxy-rs to set system proxy on windows (#5846)
* refactor: use sysproxy-rs to set system proxy on windows

* fix: remove download sysproxy.exe task

* chore: unified processing system proxy reset

* docs: update Changelog.md
2025-12-18 20:23:29 +08:00
wonfen b282217e5c doc: update promotion 2025-12-18 18:42:18 +08:00
Slinetrac 4b29a140b5 docs: Changelog.md 2025-12-17 16:57:34 +08:00
Slinetrac b6af7b7440 fix(tun): set auto-redirect default to false to improve cross-distro compatibility #5845
Closes #5845
2025-12-17 16:20:06 +08:00
Tunglies bcb8e831c5 fix(runtime): aovid force re-reresh runtime after create proxy chain 2025-12-17 12:52:25 +08:00
Slinetrac a9e6391417 feat(layout/nav): add reset-to-default menu order action 2025-12-17 11:33:31 +08:00
Slinetrac 721929a2a1 refactor(layout/nav): extract nav menu order logic into hook 2025-12-17 11:06:38 +08:00
Tunglies b35d0ac16f chore: prerelease 2.4.4-rc.1 2025-12-16 16:05:48 +08:00
Slinetrac 3198c3f023 chore(deps): bump npm deps 2025-12-16 11:48:18 +08:00
Slinetrac a3df3fc2a6 refactor(uri-parser): centralize scheme dispatch and harden parsing 2025-12-15 14:46:21 +08:00
Tunglies 507d85ac2f fix: update dependencies in Cargo.lock and Cargo.toml for compatibility 2025-12-15 14:26:54 +08:00
Tunglies 236e5c56c3 fix: update windows-sys and socket2 dependencies in Cargo.lock 2025-12-15 14:23:42 +08:00
Tunglies 5b63f350ed fix: update edition and rust-version fields in Cargo.toml files 2025-12-15 14:17:56 +08:00
Tunglies 5f885bbe32 fix: update clash_verge_service_ipc to version 2.0.26 and kode-bridge to version 0.3.5 2025-12-15 14:09:33 +08:00
oomeow 6e170993dc docs: update Changelog.md 2025-12-14 13:39:54 +08:00
oomeow ee6b857336 perf: cache listen functions to prevent always repeatedly remove and re-add listener on useLayoutEvents hook 2025-12-14 13:06:34 +08:00
Slinetrac 195f470bca docs: Changelog.md 2025-12-14 10:07:39 +08:00
xuvjso 37c2428f49 fix: head state not updated when using config.yaml as current profile (#5827) 2025-12-14 10:01:46 +08:00
Slinetrac 34793f9880 chore(husky): support cargo workspace 2025-12-13 20:28:25 +08:00
Slinetrac 04e50824f1 chore(issue): adjust issue template 2025-12-13 20:00:18 +08:00
oomeow da7faf815f Revert "perf: improve check tun mode available when update tray menu"
This reverts commit af7b33d35b.
Closes: https://github.com/clash-verge-rev/clash-verge-rev/issues/5806
2025-12-13 18:26:27 +08:00
oomeow af9b67186e perf: handle tray icon event to reduce always spawn thread 2025-12-13 16:49:55 +08:00
oomeow c5fa64a213 perf: use blocking recv method reduce unnecessary polling overhead. 2025-12-13 16:10:15 +08:00
oomeow 5c42658a2e fix: init work config and resources twice 2025-12-13 15:21:22 +08:00
oomeow 3c1bcd7d72 fix: macOS tray icon color flickers during app launch 2025-12-13 13:46:49 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 328803b894 chore(deps): update dependency react-i18next to v16.5.0 (#5808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 12:08:51 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> fbe44cf9c1 chore(deps): update actions/upload-artifact action to v6 (#5812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 12:08:29 +08:00
Slinetrac 6db559f346 chore(i18n): optimize proxy strategy names and GeoSite capitalization #5811
Closes #5811
2025-12-13 11:19:59 +08:00
Slinetrac f0c7aca3ff fix(window): hover effect of the minimize and close button does not disappear
`5bb4539e3f2d04288bf52164fdbf47bcaf949aea` was accidentally reverted in `8316c75c7836af254257d6be338d99c5c4c5923c`, so we reapply it here.
2025-12-12 17:27:05 +08:00
Slinetrac 8316c75c78 Revert "refactor(window): improve WindowProvider implementation"
May break frontend input on macOS
This reverts commit d8e386e3
2025-12-12 16:45:37 +08:00
Tunglies e56a989944 fix: improve service availability checks #5788 2025-12-12 16:07:36 +08:00
Tunglies cea78289c1 Revert "fix: service unexpected status after restart app in unix (#5768)"
This reverts commit 2995f61087.
2025-12-12 12:46:51 +08:00
Slinetrac 1a2d45555d docs: unify language list separators 2025-12-12 09:31:00 +08:00
Mehdi BahramiandSlinetrac 11879e224c docs(i18n): add README_fa.md (#5792)
* Create README_fa.md

in Persian

* Update README_fa.md

Readme file in Persian

* Add links for Persian and Korean in README_es

Added links for Persian and Korean languages in the README.

* Add Persian link to language options in README

Added Persian language link to the README.

* Add Persian link to Japanese README

* Add Persian link to Korean README

* Add links for Korean and Persian in README_ru

Added links to Korean and Persian translations in the README.

* update

fix error

* Add Persian language link to README

update

* Update link to Persian README in multiple languages section

fix

* Update README_fa.md

* style: prettier

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-12-12 09:23:36 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> c8628f32df chore(deps): update npm dependencies to v19.2.3 (#5802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 08:41:19 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 1bb1cb3511 chore(deps): update actions/cache action to v5 (#5801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 08:38:28 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 711aaf05d3 chore(deps): update npm dependencies to v19.2.2 (#5800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 08:38:14 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 430c946b61 chore(deps): update dependency node to v24.12.0 (#5799)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-12 08:37:49 +08:00
hankandTunglies 2995f61087 fix: service unexpected status after restart app in unix (#5768)
* fix: 修复macos重启应用后需要重设服务器模式问题

* chore: remove package-lock.json (using pnpm)

* Delete test.sh

* refactor(lifecycle): remove unnecessary conditional compilation for macOS and Windows

* refactor(timing): remove conditional compilation for service wait durations on Windows and macOS

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-11 20:09:27 +08:00
Sline 2aaa91f4cb perf(connections): improve scrolling performance on macOS (#5787) 2025-12-11 17:54:04 +08:00
Mehdi BahramiandSline 104fd11b04 Update settings.json (#5791)
* Update settings.json

Add translation to Persian

* Update src/locales/fa/settings.json

---------

Co-authored-by: Sline <realakayuki@gmail.com>
2025-12-11 17:39:04 +08:00
Mehdi Bahrami c7dd93004e chore(i18n): fa tunMode notifications (#5790)
translate some
2025-12-11 16:43:07 +08:00
Slinetrac 86dcf0bdcf fix: proxy chain #5776
Closes #5776
2025-12-11 16:23:43 +08:00
Slinetrac 09a4e7e083 docs: Changelog.md 2025-12-11 12:12:33 +08:00
UndoandSline 5bb4539e3f fix(window): hover effect of the minimize and close button does not disappear (#5786)
* fix(window): hover effect of the minimize and close button does not disappear

* docs: add necessary comments

---------

Co-authored-by: Sline <realakayuki@gmail.com>
2025-12-11 12:02:05 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> cad1cf2895 chore(deps): update dependency react-i18next to v16.4.1 (#5780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 02:42:42 +00:00
Tunglies 623ed69a37 fix(logging): adjust conditional compilation for NoModuleFilter based on tracing-full feature 2025-12-11 08:03:32 +08:00
Tunglies 7838fa1e75 refactor(tray): remove timer for updating tray in plan, use update manually to prevent needless refresh to cause visuual flash 2025-12-10 23:40:17 +08:00
Tunglies dd34ced070 fix(service): await for service operations, prevent too early to check service available 2025-12-10 23:29:11 +08:00
Tunglies 66483d0ef6 docs: add section on signing commits to CONTRIBUTING.md 2025-12-10 22:22:53 +08:00
Slinetrac 54858ce1fb chore: move onlyBuiltDependencies to package.json 2025-12-10 17:56:12 +08:00
Tunglies dfa5cff1b7 feat(proxy-groups): filter available groups to show only Selector types in chain mode 2025-12-10 10:04:20 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 2b552d6ddb chore(deps): update dependency @tauri-apps/cli to v2.9.6 (#5769)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 19:48:05 +00:00
oomeow af7b33d35b perf: improve check tun mode available when update tray menu 2025-12-09 19:06:57 +08:00
Slinetrac d8e386e394 refactor(window): improve WindowProvider implementation 2025-12-09 16:42:29 +08:00
Slinetrac bae65a523a chore: rename files to kebab-case and update imports 2025-12-09 13:52:42 +08:00
Slinetrac aa767695af chore(types): consolidate traffic monitor types into shared globals 2025-12-09 13:27:46 +08:00
wonfen 494e888b52 fix: typo 2025-12-08 11:26:20 +08:00
Tunglies 5292067284 chore: bump pnpm version to 10.24.0 2025-12-07 21:36:35 +08:00
Sline 9286e921a6 chore(linux): remove WebKit DMABUF env setup logic (#5758) 2025-12-07 16:06:54 +08:00
oomeowandTunglies 3ab985aacc fix: failed to restart core (#5754)
* fix: failed to restart core after install service

* chore: update tauri-plugin-mihomo deps

* fix: update dependencies for tauri-plugin-mihomo and related packages

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-07 12:42:32 +08:00
Slinetrac 418fc1c702 chore(vite): add semantic chunk naming 2025-12-07 12:35:39 +08:00
Slinetrac b5da9c9629 chore: update Cargo.lock for sysproxy-rs 2025-12-06 22:53:17 +08:00
oomeow 86936d5322 fix: wrong service status when handle service status error 2025-12-06 20:34:41 +08:00
oomeow c1489647d1 fix: update service status when service is ready 2025-12-06 20:22:45 +08:00
Tunglies af0a3be66d chore: update browserlist-db 2025-12-06 20:15:55 +08:00
140 changed files with 3576 additions and 3542 deletions
+3 -3
View File
@@ -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:
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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
+2 -2
View File
@@ -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') }}"
+6 -6
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+9
View File
@@ -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>
+18 -11
View File
@@ -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) 独立服务器全力支持,
+2 -2
View File
@@ -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 }
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+124
View File
@@ -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>
## پیش‌نمایش
| تاریک | روشن |
| ----------------------------------- | ------------------------------------- |
| ![Dark Preview](./preview_dark.png) | ![Light Preview](./preview_light.png) |
## نصب
برای دانلود فایل نصبی متناسب با پلتفرم خود، به [صفحه انتشار](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
]
}
}
+1420 -1396
View File
File diff suppressed because it is too large Load Diff
-7
View File
@@ -1,7 +0,0 @@
onlyBuiltDependencies:
- "@parcel/watcher"
- "@swc/core"
- core-js
- es5-ext
- esbuild
- unrs-resolver
+1 -1
View File
@@ -45,6 +45,6 @@
groupName: "github actions",
},
],
postUpdateOptions: ["pnpmDedupe", "updateCargoLock"],
postUpdateOptions: ["pnpmDedupe"],
ignoreDeps: ["criterion"],
}
+3 -3
View File
@@ -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
View File
@@ -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,
+5 -4
View File
@@ -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"
+2 -2
View File
@@ -25,8 +25,8 @@ notifications:
title: 应用已隐藏
body: Clash Verge 正在后台运行。
service:
adminInstallPrompt: 安装服务需要管理员权限
adminUninstallPrompt: 写在服务需要管理员权限
adminInstallPrompt: 安装 Clash Verge 服务需要管理员权限
adminUninstallPrompt: 卸载 Clash Verge 服务需要管理员权限
tray:
dashboard: 仪表板
ruleMode: 规则模式
+1 -1
View File
@@ -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(())
}
+6 -2
View File
@@ -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());
+3
View File
@@ -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?;
+7 -5
View File
@@ -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;
}
}
}
}
+24 -14
View File
@@ -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
View File
@@ -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(())
}
-19
View File
@@ -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
+22 -12
View File
@@ -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;
+1 -1
View File
@@ -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;
+17 -85
View File
@@ -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
View File
@@ -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;
}
+2 -2
View File
@@ -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 -505
View File
@@ -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."
);
}
}
+18 -15
View File
@@ -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 -1
View File
@@ -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,
+1 -1
View File
@@ -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": {
+29 -9
View File
@@ -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";
+1 -1
View File
@@ -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";
+2 -2
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
// 样式化组件 - 类型框
+42 -19
View File
@@ -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={{
+30 -5
View File
@@ -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([]);
}
};
+1 -9
View File
@@ -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;
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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;
+5 -5
View File
@@ -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";
+1 -1
View File
@@ -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";
+2 -2
View File
@@ -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";
@@ -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;
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
-9
View File
@@ -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;
+1
View File
@@ -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