Compare commits

..
59 Commits
Author SHA1 Message Date
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
113 changed files with 3195 additions and 3125 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-rc.1"
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.1"
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#279a5cf1c4470d0b28af9a5c3fb7c2fcb503a906"
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"
+4
View File
@@ -26,6 +26,9 @@
- 修复 macOS 在安装和卸载服务时提示与操作不匹配
- 修复菜单排序模式拖拽异常
- 修复托盘菜单代理组前的异常勾选状态
- 修复 Windows 下自定义标题栏按钮在最小化 / 关闭后 hover 状态残留
- 修复直接覆盖 `config.yaml` 使用时无法展开代理组
- 修复 macOS 下应用启动时系统托盘图标颜色闪烁
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
@@ -61,6 +64,7 @@
- 优化前端 WebSocket 连接机制
- 改进旧版 Service 需要重新安装检测流程
- 优化 macOS, Linux 和 Windows 系统信号处理
- 链式代理仅显示 Selector 类型规则组
</details>
+2 -1
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
+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 }
+2 -1
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
+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>
## Предпросмотр
+31 -21
View File
@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.4.4-rc",
"version": "2.4.4-rc.1",
"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",
"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.10.1",
"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.25",
"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.96.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.0",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@swc/core",
"core-js",
"es5-ext",
"esbuild",
"unrs-resolver"
]
}
}
+1415 -1391
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([
+5 -4
View File
@@ -1,14 +1,14 @@
[package]
name = "clash-verge"
version = "2.4.4-rc"
version = "2.4.4-rc.1"
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: 规则模式
+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(())
}
}
-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;
+3 -9
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, "初始化已启动");
+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."
);
}
}
+15 -14
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;
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version": "2.4.4-rc",
"version": "2.4.4-rc.1",
"$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": {
@@ -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;
+1 -1
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";
@@ -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 -1
View File
@@ -46,7 +46,7 @@
"DOMAIN-SUFFIX": "Match domain suffix (DOMAIN-SUFFIX)",
"DOMAIN-KEYWORD": "Match domain keyword (DOMAIN-KEYWORD)",
"DOMAIN-REGEX": "Match domain using regex (DOMAIN-REGEX)",
"GEOSITE": "Match domains in Geosite (GEOSITE)",
"GEOSITE": "Match domains in GeoSite (GEOSITE)",
"GEOIP": "Match IP country code (GEOIP)",
"SRC-GEOIP": "Match source IP country code (SRC-GEOIP)",
"IP-ASN": "Match IP ASN (IP-ASN)",
+46 -46
View File
@@ -15,7 +15,7 @@
"systemProxy": "پراکسی سیستم"
},
"tooltips": {
"autoLaunchAdmin": "Administrator mode may not support auto launch",
"autoLaunchAdmin": "حالت ادمین ممکن است از راه‌اندازی خودکار پشتیبانی نکند",
"silentStart": "برنامه را در حالت پس‌زمینه بدون نمایش پانل اجرا کنید"
},
"fields": {
@@ -24,8 +24,8 @@
},
"notifications": {
"tunMode": {
"autoDisabled": "TUN Mode automatically disabled due to service unavailable",
"autoDisableFailed": "Failed to disable TUN Mode automatically"
"autoDisabled": "حالت TUN به دلیل عدم دسترسی به سرویس، به طور خودکار غیرفعال می‌شود",
"autoDisableFailed": "غیرفعال کردن خودکار حالت TUN ناموفق بود"
}
}
},
@@ -33,11 +33,11 @@
"tooltips": {
"systemProxy": "به امکانات تنظیم پروکسی سیستم عامل دسترسی پیدا کنید. اگر فعال‌سازی ناموفق بود، پروکسی سیستم عامل را به‌صورت دستی تغییر دهید",
"tunMode": "حالت Tun (NIC مجازی): تمام ترافیک سیستم را ضبط می کند، وقتی فعال باشد، نیازی به فعال کردن پروکسی سیستم نیست.",
"tunUnavailable": "TUN requires Service Mode or Admin Mode"
"tunUnavailable": "TUN به حالت سرویس یا حالت ادمین نیاز دارد"
},
"actions": {
"installService": "نصب سرویس",
"uninstallService": "Uninstall Service"
"uninstallService": "حذف سرویس"
},
"fields": {
"systemProxy": "پراکسی سیستم",
@@ -47,22 +47,22 @@
"externalController": {
"title": "کنترل‌کننده خارجی",
"fields": {
"enable": "Enable External Controller",
"enable": "فعال کردن کنترل‌کننده خارجی",
"address": "کنترل‌کننده خارجی",
"secret": "رمز اصلی"
},
"placeholders": {
"address": "Required",
"address": "مورد نیاز",
"secret": "توصیه شده"
},
"tooltips": {
"copy": "Copy to clipboard"
"copy": "کپی در کلیپ بورد"
},
"messages": {
"addressRequired": "Controller address cannot be empty",
"secretRequired": "Secret cannot be empty",
"copyFailed": "Failed to copy",
"controllerCopied": "Controller address copied to clipboard",
"addressRequired": "آدرس کنترلر نمی‌تواند خالی باشد",
"secretRequired": "راز نمی‌تواند خالی باشد",
"copyFailed": "کپی نشد",
"controllerCopied": "آدرس کنترلر در کلیپ بورد کپی شد",
"secretCopied": "Secret copied to clipboard"
}
},
@@ -73,10 +73,10 @@
"allowedOrigins": "Allowed Origins"
},
"placeholders": {
"origin": "Please enter a valid url"
"origin": "لطفا یک url معتبر وارد کنید"
},
"actions": {
"add": "Add"
"add": "افزودن"
},
"messages": {
"alwaysIncluded": "Always included origins: {{urls}}"
@@ -95,7 +95,7 @@
"form": {
"fields": {
"allowLan": "اجازه LAN",
"dnsOverwrite": "DNS Overwrite",
"dnsOverwrite": "بازنویسی DNS",
"ipv6": "IPv6",
"unifiedDelay": "معادلDELAY",
"logLevel": "سطح لاگ",
@@ -157,11 +157,11 @@
"liteMode": "رابط کاربری گرافیکی را ببندید و فقط هسته را در حال اجرا نگه دارید"
},
"actions": {
"copyVersion": "Copy Version"
"copyVersion": "کپی نسخه"
},
"notifications": {
"latestVersion": "در حال حاضر در آخرین نسخه",
"versionCopied": "Version copied to clipboard"
"versionCopied": "نسخه در کلیپ بورد کپی شد"
},
"fields": {
"backupSetting": "تنظیمات پشتیبان گیری",
@@ -263,13 +263,13 @@
"autoEnter": "Auto Enter LightWeight Mode"
},
"tooltips": {
"autoEnter": "Enable to automatically activate LightWeight Mode after the window is closed for a period of time"
"autoEnter": "فعال کردن حالت LightWeight به صورت خودکار پس از بسته شدن پنجره برای مدت زمانی خاص"
},
"fields": {
"delay": "Auto Enter LightWeight Mode Delay"
"delay": "تأخیر ورود خودکار به حالت LightWeight"
},
"messages": {
"autoEnterHint": "When closing the window, LightWeight Mode will be automatically activated after {{n}} minutes"
"autoEnterHint": "هنگام بستن پنجره، حالت LightWeight پس از {{n}} دقیقه به طور خودکار فعال می‌شود"
}
},
"backup": {
@@ -325,18 +325,18 @@
}
},
"manual": {
"title": "Manual backup",
"local": "Creates a snapshot on this device, stored under the app data directory.",
"webdav": "Upload a snapshot to your WebDAV server once credentials are set.",
"configureWebdav": "Configure WebDAV"
"title": "پشتیبان گیری دستی",
"local": "یک اسنپ‌شات روی این دستگاه ایجاد می‌کند که در پوشه‌ی داده‌های برنامه ذخیره می‌شود.",
"webdav": "پس از تنظیم اعتبارنامه‌ها، یک اسنپ‌شات (عکس فوری) در سرور WebDAV خود آپلود کنید.",
"configureWebdav": "پیکربندی WebDAV"
},
"history": {
"title": "Backup history",
"title": "تاریخچه پشتیبان گیری",
"summary": "{{count}} backups • latest {{recent}}",
"empty": "No backups available"
"empty": "هیچ نسخه پشتیبان در دسترس نیست"
},
"webdav": {
"title": "WebDAV settings"
"title": "پیکربندی WebDAV"
},
"table": {
"filename": "نام فایل",
@@ -431,8 +431,8 @@
"autoRedirect": "Auto Redirect"
},
"tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
"dnsHijack": "لطفا برای جدا کردن چندین سرور DNS از , استفاده کنید.",
"autoRedirect": "پیکربندی خودکار ریدایرکت‌های TCP در nftables/iptables"
},
"messages": {
"applied": "تنظیمات اعمال شد"
@@ -440,11 +440,11 @@
},
"dns": {
"dialog": {
"title": "DNS Overwrite",
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite enabled"
"title": "بازنویسی DNS",
"warning": "اگر با این تنظیمات آشنا نیستید، لطفاً آنها را تغییر ندهید و DNS Overwrite را فعال نگه دارید."
},
"sections": {
"general": "DNS Settings",
"general": "تنظیمات DNS",
"fallbackFilter": "Fallback Filter Settings",
"hosts": "Hosts Settings"
},
@@ -464,7 +464,7 @@
},
"respectRules": {
"label": "Respect Rules",
"description": "DNS connections follow routing rules"
"description": "اتصالات DNS از قوانین مسیریابی پیروی می‌کنند"
},
"useHosts": {
"label": "Use Hosts",
@@ -484,11 +484,11 @@
},
"nameserver": {
"label": "Nameserver",
"description": "List of DNS servers, comma separated"
"description": "فهرست سرورهای DNS، جدا شده با کاما"
},
"fallback": {
"label": "Fallback",
"description": "List of fallback DNS servers, comma separated"
"description": "فهرست سرورهای DNS جایگزین، جدا شده با کاما"
},
"proxy": {
"label": "Proxy Server Nameserver",
@@ -516,21 +516,21 @@
"description": "IP CIDRs not using fallback servers, comma separated"
},
"fallbackDomain": {
"label": "Fallback Domain",
"description": "Domains using fallback servers, comma separated"
"label": "دامنه جایگزین",
"description": "دامنه‌هایی که از سرورهای جایگزین استفاده می‌کنند، با کاما از هم جدا شده‌اند"
},
"hosts": {
"label": "Hosts",
"description": "Custom domain to IP or domain mapping"
"label": "میزبان‌ها",
"description": "تبدیل دامنه به IP یا نگاشت دامنه سفارشی"
}
},
"messages": {
"saved": "DNS settings saved",
"configError": "DNS configuration error:"
"saved": "تنظیمات DNS ذخیره شد",
"configError": "خطای پیکربندی DNS:"
},
"errors": {
"invalid": "Invalid configuration",
"invalidYaml": "Invalid YAML format"
"invalid": "پیکربندی نامعتبر",
"invalidYaml": "قالب YAML نامعتبر است"
}
},
"webUI": {
@@ -587,8 +587,8 @@
"uninstallSuccess": "سرویس با موفقیت حذف نصب شد"
},
"updater": {
"withClashProxySuccess": "Update with Clash proxy successfully",
"withClashProxyFailed": "Update failed even with Clash proxy"
"withClashProxySuccess": "با موفقیت با پروکسی کلش به‌روزرسانی شد",
"withClashProxyFailed": "به‌روزرسانی حتی با پروکسی کلش هم انجام نشد"
}
}
},
@@ -599,7 +599,7 @@
},
"clashService": {
"installing": "در حال نصب سرویس...",
"uninstalling": "Uninstalling Service..."
"uninstalling": "در حال حذف سرویس..."
}
}
}
+3 -3
View File
@@ -80,10 +80,10 @@
"relay": "根据定义的代理链传递"
},
"policies": {
"DIRECT": "直连 (DIRECT)",
"DIRECT": "直接连接 (DIRECT)",
"REJECT": "拦截请求 (REJECT)",
"REJECT-DROP": "抛弃请求 (REJECT-DROP)",
"PASS": "跳过此规则 (PASS)"
"REJECT-DROP": "丢弃请求 (REJECT-DROP)",
"PASS": "跳过此项 (PASS)"
}
}
}
+1 -1
View File
@@ -46,7 +46,7 @@
"DOMAIN-SUFFIX": "匹配域名后缀 (DOMAIN-SUFFIX)",
"DOMAIN-KEYWORD": "匹配域名关键字 (DOMAIN-KEYWORD)",
"DOMAIN-REGEX": "匹配域名正则表达式 (DOMAIN-REGEX)",
"GEOSITE": "匹配 Geosite 内的域名 (GEOSITE)",
"GEOSITE": "匹配 GeoSite 内的域名 (GEOSITE)",
"GEOIP": "匹配 IP 所属国家代码 (GEOIP)",
"SRC-GEOIP": "匹配来源 IP 所属国家代码 (SRC-GEOIP)",
"IP-ASN": "匹配 IP 所属 ASN (IP-ASN)",
+3 -3
View File
@@ -80,10 +80,10 @@
"relay": "根據定義的代理鏈傳送"
},
"policies": {
"DIRECT": "直連 (DIRECT)",
"REJECT": "拒絕請求 (REJECT)",
"DIRECT": "直接連線 (DIRECT)",
"REJECT": "攔截請求 (REJECT)",
"REJECT-DROP": "丟棄請求 (REJECT-DROP)",
"PASS": "跳過此規則 (PASS)"
"PASS": "跳過此項 (PASS)"
}
}
}
+1 -1
View File
@@ -46,7 +46,7 @@
"DOMAIN-SUFFIX": "配對網域後綴 (DOMAIN-SUFFIX)",
"DOMAIN-KEYWORD": "配對網域關鍵字 (DOMAIN-KEYWORD)",
"DOMAIN-REGEX": "配對網域正規表示式 (DOMAIN-REGEX)",
"GEOSITE": "配對 Geosite 內的網域 (GEOSITE)",
"GEOSITE": "配對 GeoSite 內的網域 (GEOSITE)",
"GEOIP": "配對 IP 所屬國家代碼 (GEOIP)",
"SRC-GEOIP": "配對來源 IP 所屬國家代碼 (SRC-GEOIP)",
"IP-ASN": "配對 IP 所屬 ASN (IP-ASN)",
@@ -1,4 +1,4 @@
import { showNotice } from "@/services/noticeService";
import { showNotice } from "@/services/notice-service";
type NavigateFunction = (path: string, options?: any) => void;
type TranslateFunction = (key: string) => string;
+4 -1
View File
@@ -129,8 +129,9 @@ const ConnectionsPage = () => {
height: "100%",
display: "flex",
flexDirection: "column",
overflow: "auto",
overflow: "hidden",
borderRadius: "8px",
minHeight: 0,
}}
header={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
@@ -243,6 +244,8 @@ const ConnectionsPage = () => {
style={{
flex: 1,
borderRadius: "8px",
WebkitOverflowScrolling: "touch",
overscrollBehavior: "contain",
}}
data={filterConn}
itemContent={(_, item) => (

Some files were not shown because too many files have changed in this diff Show More