Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot] a097009679 Initial plan 2026-01-16 04:44:21 +00:00
Tunglies b146013984 perf: optimize filename matching logic in IProfiles by using a static regex 2026-01-16 12:13:59 +08:00
Tunglies 09dcb072c3 perf: simplify current_mapping and profiles_preview methods in IProfiles for improved readability 2026-01-16 12:05:17 +08:00
Tunglies c5233a9c2c perf: refactor PrfOption merge logic and streamline update_item method in IProfiles 2026-01-16 11:47:36 +08:00
Tunglies f246ea4469 perf: add PartialEq derive to PrfSelected and PrfExtra structs for improved comparison 2026-01-16 11:29:57 +08:00
Tunglies 733f7a139d perf: enhance profile reordering logic and adjust logging level 2026-01-15 23:59:27 +08:00
Tunglies 357a683f92 perf: improve config processing 2026-01-15 06:50:21 +08:00
Tunglies 5c9b46f031 chore: bump version to prerelease 2.4.5-rc.1 2026-01-14 16:55:27 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> f5e75d5287 chore(deps): update dependency node to v24.13.0 (#6087)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-14 14:40:47 +08:00
Slinetrac c2d8277a1a fix(connections): allow full-width header sorting without triggering on resize 2026-01-14 11:23:50 +08:00
Tunglies 66e98518a7 chore(ci): update autobuild setup for ARM architecture support 2026-01-13 18:42:41 +08:00
Tunglies 089b73bbfd chore(deps): update clash_verge_service_ipc to version 2.0.29 (#6073) 2026-01-13 18:30:54 +08:00
Slinetrac d2c52d09e1 chore(renovate): disable lockfile maintenance automerge 2026-01-12 15:10:52 +08:00
Slinetrac 84143ec761 chore(deps): bump npm deps 2026-01-12 14:50:54 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Slinetrac
f451a26f8c chore(deps): lock file maintenance (#6063)
* chore(deps): lock file maintenance

* chore(deps): update Cargo.toml

* chore(deps): use git repo until the next release

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2026-01-12 14:45:33 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> e1220a189b chore(deps): lock file maintenance npm dependencies (#6064)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 05:12:01 +00:00
Tunglies 57d4149807 fix(config): improve runtime config fallback handling 2026-01-11 15:27:54 +08:00
Slinetrac 86c3b241b1 docs: Changelog.md 2026-01-10 11:04:55 +08:00
Sline a49000712d feat(tun-viewer): route-exclude-address GUI support (#6053) 2026-01-10 10:50:44 +08:00
歳納七夏 35b2066d4c build(tauri): add libayatana-appindicator3 dependency for linux packages (#6051) 2026-01-10 08:42:57 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 92e0762fc4 chore(deps): update dependency @actions/github to v7 (#6042)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 00:27:00 +08:00
Slinetrac 6b8630d357 docs: Changelog.md 2026-01-08 22:44:11 +08:00
Slinetrac a1e77070f0 chore(deps): bump clash-verge-service-ipc to 2.0.29 2026-01-08 22:29:10 +08:00
Slinetrac 6926744ca2 docs: Changelog.md 2026-01-08 14:12:48 +08:00
Slinetrac 13855b9bc2 perf(tun-viewer): run enhanceProfiles in background to avoid save blocking 2026-01-08 14:03:00 +08:00
Slinetrac 1889f18183 feat(notice): override context menu to copy error details 2026-01-07 13:17:56 +08:00
Slinetrac a981be80ef refactor(base): expand barrel exports and standardize imports 2026-01-06 15:02:10 +08:00
Slinetrac 60d3a1927b docs: Changelog.md 2026-01-05 16:18:44 +08:00
Slinetrac 620841592f fix(scheme): prevent double-decoding of subscription URLs #2609
Closes #2609
2026-01-05 15:36:01 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Slinetrac
2128e1f788 chore(deps): lock file maintenance npm dependencies (#6015)
* chore(deps): lock file maintenance npm dependencies

* chore(deps): bump npm deps

* chore: fix eslint ref-name

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2026-01-05 13:53:22 +08:00
Slinetrac 256a3f697b chore(deps): update Cargo.toml 2026-01-05 13:26:28 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> a701450362 chore(deps): lock file maintenance (#6014)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 10:47:37 +08:00
Tunglies 9e4e0c81a4 refactor: clean up and improve code structure (#6010) 2026-01-04 19:21:45 +08:00
oomeow 421bbd090e feat: reconfig log dynamically (#5724) 2026-01-04 16:56:16 +08:00
Tunglies 4adf678480 chore: update Cargo.lock dependencies and increment tauri-plugin-mihomo version to 0.1.3 2026-01-04 16:29:01 +08:00
Slinetrac a9a782d5c9 docs: Changelog.md 2026-01-04 15:05:12 +08:00
Sline ee5e5ee8a6 feat(sysproxy-viewer): add visual editor for bypass list with chips display (#6007) 2026-01-04 14:58:50 +08:00
Slinetrac a940445081 chore(deps): revert tray-icon patch
Patch PR is merged.
2026-01-04 11:47:32 +08:00
Tunglies 65653594c7 chore: allow to update Cargo.lockl from git repo 2026-01-04 06:59:36 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> ac8f62bea2 chore(deps): update tauri-apps/tauri-action action to v0.6.1 (#6005)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-03 20:25:57 +00:00
Tunglies eb8ba8b369 refactor(tray): replace Arc<str> with Cow<'static, str> for menu texts and update tray event handling 2026-01-03 19:25:36 +08:00
Slinetrac c18821288e feat(sysproxy-viewer): make proxy bypass validation optional #4012 2026-01-03 17:10:10 +08:00
Slinetrac 7d40410dea chore: update tray-icon patch 2026-01-03 15:44:46 +08:00
AetherWing 349be20a6c chore(windows): remove redundant 3s delay from autostart task (#5996)
* fix(tray): retry tray creation with delay on Windows

* fix(tray): retry tray creation with delay on Windows

* chore(windows): remove redundant 3s delay from autostart task

* chore(windows): remove redundant 3s delay from autostart task
2026-01-02 15:18:01 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 1901a6c97c chore(deps): update dependency globals to v17 (#5997)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-02 15:07:31 +08:00
Slinetrac bb72b92ae9 refactor(tray): unify tray icon retrieval into get_tray_icon 2026-01-02 14:28:17 +08:00
Slinetrac 8a1740d38b fix(tray): apply patch for tray-icon #5984
Closes #5984.
2026-01-02 13:54:47 +08:00
Tunglies d75d3bd86e fix(proxy): update system proxy state references to use configState 2026-01-01 17:40:56 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> b277a1e760 chore(deps): update dependency react-i18next to v16.5.1 (#5994)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-01 16:55:15 +08:00
Slinetrac 522eccdd0e refactor(icon): extract icon cache logic into reusable hook 2026-01-01 16:35:36 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Slinetrac
f3b9eedcf7 chore(deps): update rust crate reqwest to 0.13.1 (#5990)
* chore(deps): update rust crate reqwest to 0.13.0

* fix: replace rustls-tls with rustls and update lock file

* fix(webdav): use tauri_plugin_http re-exported reqwest client

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-12-31 23:53:49 +08:00
Tunglies 3bbcdbe5ca fix(service): update update clash_verge_service_ipc to 2.0.27
chore(changelog): add IPC permission restrictions for macOS and Linux

refactor(service): remove outdated service version check logic
2025-12-31 21:35:34 +08:00
TungliesandKaguraNaku cceb0a6eb4 fix(permissions): manage umask for sidecar process in CoreManager on Unix
Co-authored-by: KaguraNaku <97681505+KaguraNaku@users.noreply.github.com>
2025-12-31 21:05:05 +08:00
Slinetrac cb5a2e7ce3 perf(proxy-chain): avoid duplicate proxy refresh in chain panel #5855 2025-12-31 16:42:56 +08:00
Slinetrac 609008f087 refactor(uri-parser): split parser into folder-based modules 2025-12-31 14:40:45 +08:00
Slinetrac bae3576e93 fix: update tauri-plugin-mihomo version in pnpm-lock.yaml 2025-12-30 23:58:43 +08:00
Tunglies 9ce343fb45 perf(i18n): update translate function to use Cow to aovid allocate 2025-12-30 18:32:19 +08:00
Tunglies cf08628200 fix(config): update home_cards reference to use as_ref() for improved handling 2025-12-30 18:09:10 +08:00
Tunglies c06c15450f perf(draft): update with_data_modify to use T instead of Box<T> for better performance
Performance improved around 11.7%, avoid to allocate stack and copy to heap.
2025-12-30 17:51:50 +08:00
Slinetrac 772b87e733 feat: add GUI support for AnyTLS/Mieru/Sudoku and AnyTLS URI parsing 2025-12-30 15:15:55 +08:00
Slinetrac c80c659180 fix(dns-viewer): disable spellcheck on DNS viewer text fields #5948
Closes #5948
2025-12-30 13:44:56 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 0cde6cfce9 chore(deps): update dependency react-error-boundary to v6.0.1 (#5969)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 10:33:24 +08:00
Slinetrac e6a0369036 fix(ui): hide scrollbars on side menu and hover jump navigator #5945
Closes #5945
2025-12-29 10:26:34 +08:00
Slinetrac ca50e35435 chore(deps): bump deps 2025-12-28 17:55:58 +08:00
Slinetrac 0193ba7bf9 refactor(seq): use if let filter for selector group dedup 2025-12-28 11:05:28 +08:00
Slinetrac c40cdf6b55 fix(seq): prepend newly added proxies to first selector group 2025-12-28 10:55:27 +08:00
Sline a82bcbe86e feat(proxy): auto-append new proxies to first selector group (#5965)
* feat(proxy): auto-append new proxies to first selector group

* docs: Changelog.md
2025-12-28 10:33:10 +08:00
Slinetrac 895e54f7ec fix(config): restrict restart_core_needed mutability to non-Windows to avoid unused-mut 2025-12-28 09:45:54 +08:00
Tunglies c41db51f81 feat: add governor crate for rate limiting and improve window/tray operation handling 2025-12-27 20:27:49 +08:00
Tunglies 2c1303c2bd feat: add bitflags crate and refactor UpdateFlags implementation 2025-12-27 17:09:08 +08:00
SlineandTunglies c8aeae3f83 crate(i18n): add clash-verge-i18n crate and integrate localization support (#5961)
* crate(i18n): add clash-verge-i18n crate and integrate localization support

* refactor(service): remove redundant reinstall_service functions for Windows, Linux, and macOS

* chore(i18n): align i18n key

* feat(i18n): unify scan roots and add backend Rust/YAML support to cleanup script

* chore(i18n): add scripts to package.json

* fix(tray): initialize i18n locale before setup

* refactor(i18n): move locale initialization into Config::init_config

* fix(i18n): refresh systray tooltip on language change and correct docs reference

* fix(tray): remove unnecessary locale synchronization to improve performance

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-27 15:03:19 +08:00
Slinetrac 1b477ed0b2 chore: fix typo 2025-12-27 13:56:52 +08:00
Slinetrac 5aba848741 Revert "crate(i18n): add clash-verge-i18n crate and integrate localization support (#5959)"
This reverts commit 593751eda2.
2025-12-27 12:07:56 +08:00
TungliesandSlinetrac 593751eda2 crate(i18n): add clash-verge-i18n crate and integrate localization support (#5959)
* crate(i18n): add clash-verge-i18n crate and integrate localization support

* refactor(service): remove redundant reinstall_service functions for Windows, Linux, and macOS

* chore(i18n): align i18n key

* feat(i18n): unify scan roots and add backend Rust/YAML support to cleanup script

* chore(i18n): add scripts to package.json

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-12-27 11:33:58 +08:00
Slinetrac b53f54f3f4 fix(i18n): remove invalid i18n key usage 2025-12-27 09:24:15 +08:00
Tunglies bfb18cf003 refactor(profile): improve error handling for file not found case
refactor(merge): simplify deep_merge function signature
chore: remove unused fmt_bytes function and related tests
chore: clean up help module by removing unused macros
chore: remove format module from utils
2025-12-26 22:15:09 +08:00
Tunglies 9c6f5bc991 fix(profiles): add cleanup of orphaned files and adjust logging level 2025-12-26 17:17:18 +08:00
Slinetrac 63cd4905f9 refactor(notice): extract resolveNoticeMessage helper 2025-12-26 16:42:31 +08:00
歳納七夏 2417d064e1 feat: allow disabling proxy groups in tray icon (#5946)
* feat: allow disabling proxy groups in tray icon

* feat: allow disabling proxy groups in tray icon (update i11n)

* feat: allow disabling proxy groups in tray icon (fix)

* feat: allow disabling proxy groups in tray icon (less nesting)

* feat: allow disabling proxy groups in tray icon (changelog)
2025-12-26 16:29:05 +08:00
Slinetrac d91e19e166 chore: reorganize frontend files 2025-12-26 15:53:25 +08:00
Sline 65b4d8713d refactor: windows autostart (#5941)
* refactor(windows-autostart): switch to scheduled tasks with user/admin split

* fix(schtasks): decode stdout/stderr using Windows OEM/ANSI code pages

* refactor(ui): remove admin auto-launch warning and clean i18n

* feat(windows): user-level auto-launch via task XML

* docs: Changelog.md
2025-12-26 14:42:21 +08:00
Slinetrac a67abda72d refactor(theme): simplify useCustomTheme for Tauri only 2025-12-26 12:25:51 +08:00
Slinetrac 8e27834e35 fix(theme/windows): apply patch to tao to fix Windows theme sync 2025-12-26 12:01:44 +08:00
Slinetrac ee3f7df417 Revert "fix(theme/windows): switch to dark-light based theme detection"
This reverts commit 1c044f053f.
2025-12-26 11:59:13 +08:00
Tunglies f9b8a658a1 perf(draft): optimize memory layout by removing double indirection & implemented optimistic locking via Arc::ptr_eq for with_data_modify (#5942)
* perf(draft): optimize memory layout by removing double indirection

- Replace `Arc<Box<T>>` with `Arc<T>` to reduce pointer chasing and memory overhead.
- Leverage `Arc::from(Box<T>)` in async modify path for efficient ownership transfer.
- Fix race conditions in `edit_draft` by ensuring atomicity under write lock.
- Performance improved by ~16-24% across all operations (based on Criterion bench).

Benchmarks:
- latest_arc:     41.1ns (-24.2%)
- edit_draft:     92.2ns (-17.6%)
- apply:          89.8ns (-17.7%)
- async_modify:   66.0ns (-16.6%)

* perf(draft): implemented optimistic locking via Arc::ptr_eq for with_data_modify

Benchmarks confirm only a negligible ~2% (1.3ns) overhead for async operations, ensuring total data integrity during concurrent updates.
2025-12-25 16:44:23 +08:00
Slinetrac 1c044f053f fix(theme/windows): switch to dark-light based theme detection 2025-12-25 14:43:27 +08:00
Slinetrac 712b8ff19b refactor(linux): move Linux-specific mime.rs and workarounds.rs to utils/linux 2025-12-25 13:18:55 +08:00
Slinetrac 4ab2720ac4 refactor(tray): remove --no-tray code path and env-based tray bypass 2025-12-25 10:42:03 +08:00
Slinetrac af0e72d119 feat(linux): disable WebKit DMABUF renderer on NVIDIA GPUs at startup #5921 2025-12-25 10:34:27 +08:00
Slinetrac bd62a4ecc0 refactor(utils): extract freedesktop mimeapps handling into utils/mime 2025-12-25 10:02:07 +08:00
Tunglies 4ffb8b415f perf(traffic): optimize traffic data handling and improve performance 2025-12-24 13:40:03 +08:00
Slinetrac 0992556b4a feat(update-viewer): support rendering HTML in update markdown #5932
Closes #5932
2025-12-23 20:48:14 +08:00
Tunglies be6b53c760 ci: update Ubuntu sources to use 'noble' release and adjust package installation 2025-12-23 14:52:47 +08:00
Slinetrac 797c0f90aa chore(deps): bump npm deps 2025-12-23 11:59:49 +08:00
Slinetrac d52f00c1b1 feat(ui): add collapse/expand option to navbar context menu #5910
Closes #5910
2025-12-23 11:52:40 +08:00
Slinetrac f26abcd2a9 fix(backup): reload config.yaml and profiles.yaml after restore to avoid stale memory overwrite #5909
Closes #5909
2025-12-23 10:56:31 +08:00
Tunglies 863a80df43 ci: update Ubuntu version to 24.04 and adjust dependencies in autobuild workflow 2025-12-22 23:23:35 +08:00
oomeow 19accbd538 chore: rust related config for workspace (#5912)
* chore: move rust related config to workspace

* chore: cargo fmt
2025-12-22 15:28:55 +08:00
SukkaandSlinetrac 8e48e4ed10 chore(eslint): add eslint-plugin-react-compiler (#5918)
* chore(eslint): add `eslint-plugin-react-compiler`

* refactor: fix eslint warnings and avoid in-render mutations

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-12-22 12:51:20 +08:00
Slinetrac eafa08066d docs(notice): add example for passing Error directly with i18n key 2025-12-22 11:01:53 +08:00
Slinetrac 231517b5db chore(monaco): ignore worker errors in console 2025-12-21 23:52:16 +08:00
Slinetrac 45193e017f fix(monaco): Ensure monaco loader uses the bundled ESM instance instead of CDN 2025-12-21 21:38:05 +08:00
Slinetrac 2515deefed Revert "refactor(editor-viewer): simplify loading/save flow with timeout fallback (#5905)"
This reverts commit c84bb91f4a.
2025-12-21 20:28:38 +08:00
Sline c84bb91f4a refactor(editor-viewer): simplify loading/save flow with timeout fallback (#5905) 2025-12-21 17:10:52 +08:00
Tunglies af094bfcd7 refactor: remove port_scanner dependency and simplify port checking logic 2025-12-21 16:51:55 +08:00
Slinetrac a5752f7b00 docs: Changelog.md 2025-12-21 13:16:54 +08:00
Slinetrac 23e551e384 feat(tray): add optional inline outbound modes in tray menu #5881
Closes #5881
2025-12-21 10:33:19 +08:00
Slinetrac a0b12b8797 chore(deps): bump npm deps 2025-12-21 09:43:07 +08:00
Tunglies 16c3dcc616 fix(proxy): check if proxy port is in use #5891 2025-12-20 19:11:27 +08:00
Slinetrac 5afe11e55b fix(monaco): disable MUI Dialog focus enforcement for Monaco editor #5885
Closes #5885
2025-12-20 16:12:45 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 6f61759a39 chore(deps): update rust crate zip to v7 (#5886)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-20 13:54:25 +08:00
CactusandSlinetrac 57b17ab8d3 feat: add navigation collapse functionality to layout (#5815)
* feat(layout): add collapsible navbar toggle in UI settings

* refactor(layout): adjust collapsed navbar styles

* docs: Changelog.md

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-12-19 18:15:20 +08:00
Sline fc84dc561c feat(notice): persist toast position preference (#5621)
* feat(notice): persist toast position preference

* docs: Changelog.md
2025-12-19 18:04:23 +08:00
Sline bd8eccdcea feat(backup): add local backup import flow (#5669)
* feat(backup): add local backup import flow

* refactor(backup): robustify history listing and propagate import error details

* docs: Changelog.md
2025-12-19 17:46:31 +08:00
SlineandTunglies b4e25951b4 feat(proxy): unify filter search box and sync filter options (#5819)
* feat(proxy): unify filter search box and sync filter options

* docs: fix JSDoc placement

* refactor(search): unify string matcher and stabilize regex filtering

* fix(search): treat invalid regex as matching nothing

* docs: update changelog to include advanced filter search feature for proxy page

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-19 17:12:36 +08:00
zhuo-githubzhuocanhe <zhuocanhe>Tunglies
aa72fa9a42 fix: add DNS hijacking fix for macOS wired network(#5728)
* [bugfix][issues-2701]mac有线网络下dns劫持失败

* [bugfix][issues-2701]mac有线网络下dns劫持失败。删除弃用代码,删除打印端口日志

* fix(changelog): add DNS hijacking fix for macOS wired network

---------

Co-authored-by: zhuocanhe <zhuocanhe>
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-19 17:05:47 +08:00
Tunglies f0ae631cb0 chore: update version to 2.4.5 and enhance changelog management scripts 2025-12-19 16:51:11 +08:00
Tunglies 787463a226 chore: bump version to 2.4.4 2025-12-19 16:25:24 +08:00
Slinetrac 8d5d72957b fix(home): correct delay sorting for Current Node card #5870
Closes #5870
2025-12-19 14:24:04 +08:00
Slinetrac 7d805cb83e chore(deps): bump npm deps 2025-12-19 10:32:40 +08:00
oomeow a1286ad057 fix: always occupies hotkey globally when app launch silently (#5866)
* fix: always occupies hotkey when app launch silently

* docs: update Changelog.md

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

* fix: remove download sysproxy.exe task

* chore: unified processing system proxy reset

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

in Persian

* Update README_fa.md

Readme file in Persian

* Add links for Persian and Korean in README_es

Added links for Persian and Korean languages in the README.

* Add Persian link to language options in README

Added Persian language link to the README.

* Add Persian link to Japanese README

* Add Persian link to Korean README

* Add links for Korean and Persian in README_ru

Added links to Korean and Persian translations in the README.

* update

fix error

* Add Persian language link to README

update

* Update link to Persian README in multiple languages section

fix

* Update README_fa.md

* style: prettier

---------

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

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

* Delete test.sh

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

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

---------

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

Add translation to Persian

* Update src/locales/fa/settings.json

---------

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

* docs: add necessary comments

---------

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

* chore: update tauri-plugin-mihomo deps

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

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-07 12:42:32 +08:00
Slinetrac 418fc1c702 chore(vite): add semantic chunk naming 2025-12-07 12:35:39 +08:00
Slinetrac b5da9c9629 chore: update Cargo.lock for sysproxy-rs 2025-12-06 22:53:17 +08:00
oomeow 86936d5322 fix: wrong service status when handle service status error 2025-12-06 20:34:41 +08:00
oomeow c1489647d1 fix: update service status when service is ready 2025-12-06 20:22:45 +08:00
Tunglies af0a3be66d chore: update browserlist-db 2025-12-06 20:15:55 +08:00
oomeow d0917dae73 fix: core always run by sidecar mode after reinstall service 2025-12-06 13:27:24 +08:00
Tunglies f383a18f77 fix(release): add Rust target installation step for cross-compilation 2025-12-06 12:28:55 +08:00
Tunglies f8035710df fix(release): update Rust toolchain to version 1.91.0 and adjust installation steps 2025-12-06 12:11:45 +08:00
Tunglies 4e34eb156b chore: update version to 2.4.4-rc 2025-12-06 11:52:04 +08:00
Tunglies c997fbbeed chore: update Cargo.lock 2025-12-06 11:48:00 +08:00
Tunglies 54ba9fc98d fix(changelog): remove obsolete entry and clarify i18n support details 2025-12-06 11:44:07 +08:00
Slinetrac eb7d22f610 refactor(debug): simplify toggle flow and unify flag handling 2025-12-06 11:36:47 +08:00
Tunglies 90b1c6e153 refactor(signal): migrate signal to handling async in windows and remove windows_sys dependency (#5741)
* refactor(signal): migrate signal to handling async in windows and remove signal-hook dependency

* refactor(signal): enhance Windows signal handling with improved cleanup logic

* refactor(signal): improve exit handling in run function for async compatibility
2025-12-06 11:33:49 +08:00
Slinetrac 5797bb7f8c docs: add JSDoc for showNotice API 2025-12-06 11:16:26 +08:00
Tunglies 2ca8e6716d style: adjust rustfmt max_width to 120 2025-12-06 10:31:31 +08:00
Tunglies c888703b05 fix(service): enhance version check error handling for compatibility with older service versions 2025-12-06 10:26:03 +08:00
Tunglies cd6885f115 Revert "ci(dev): add --profile fast-release argument to Tauri build step"
This reverts commit 7ae2414067.
2025-12-06 09:28:41 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 643df7c15d chore(deps): update dependency react-i18next to v16.4.0 (#5739)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-06 09:20:09 +08:00
❤是纱雾酱哟~ ff0de29f17 Fixes traffic parsing for small values (#5737)
- Unified handler to IEC standard.
- Change handler to parse number in a unified style, even with number less than 1000.

Signed-off-by: Dragon1573 <49941141+Dragon1573@users.noreply.github.com>
2025-12-06 09:00:04 +08:00
Tunglies 7ae2414067 ci(dev): add --profile fast-release argument to Tauri build step 2025-12-06 08:14:33 +08:00
Tunglies d28075221c refactor(signal): migrate signal to handling async in unix and remove signal-hook dependency 2025-12-06 06:41:47 +08:00
Slinetrac d0c8f7fcc1 docs: Changelog.md 2025-12-05 13:55:32 +08:00
Slinetrac 28f8bb9199 fix(tray): remove checkmark from proxy group entries 2025-12-05 13:46:22 +08:00
Slinetrac 19a31450e5 fix(nav): use ListItemButton as drag handle for navigation items 2025-12-05 13:21:46 +08:00
Tunglies a0fd24bb90 refactor(sysopt): replace Mutex with RwLock for inner_proxy management 2025-12-04 23:47:34 +08:00
Tunglies f2ad62e446 chore: update clash_verge_service_ipc to version 2.0.24 , improve service prompts and improve service need reinstall process 2025-12-04 23:04:03 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 7aeaab6e5c chore(deps): update npm dependencies to v19.2.1 (#5711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 15:01:51 +08:00
Sline afee21dae4 refactor: unify Mihomo WS subscription with shared hook (#5719)
* refactor: unify Mihomo WS subscription with shared hook

* refactor: relocate clash log hook and streamline services

* docs: Changelog.md
2025-12-04 14:58:03 +08:00
Slinetrac 3cf51de850 refactor: reorganize component and hook structure; remove unused modules 2025-12-04 12:51:22 +08:00
Slinetrac 4f8ed63cf0 refactor(use-clash): centralize patch typing and port validation logic 2025-12-04 09:30:24 +08:00
Tunglies 135d14b170 chore: allow renovate automerge patch version and allow checks cargo workspace 2025-12-04 06:27:17 +08:00
Tunglies 762c991035 Revert "chore: update renovate configuration to enhance dependency management and grouping"
This reverts commit 2af7ddfdef.
2025-12-04 06:13:35 +08:00
Tunglies 357069505a chore(deps): bump clash_verge_service_ipc version to 2.0.23 2025-12-04 00:04:27 +08:00
Tunglies 2af7ddfdef chore: update renovate configuration to enhance dependency management and grouping 2025-12-03 23:57:53 +08:00
Tunglies bd24a3f0ed chore: update Changelog for WebView2 Runtime upgrade to 133.0.3065.92 2025-12-03 22:08:30 +08:00
Tunglies 127501ce49 chore(deps): bump fixed WebView2 Runtime from 109.0.1518.78 to 133.0.3065.92 2025-12-03 22:05:43 +08:00
Tunglies 666878e288 refactor(release): enhance Rust caching strategy and update file paths 2025-12-03 22:00:19 +08:00
oomeowandTunglies 9388b73c1f fix: upgrade mihomo core failed (#5646)
* fix: upgrade mihomo core failed

* fix: upgrade core failed when run core by service

* fix: app freeze when restart core on windows

* deps: bump clash-verge-rev-ipc version lock

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-03 21:48:30 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 29cf57d4b9 chore(deps): update npm dependencies (#5706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 21:27:23 +08:00
wonfen 176ad1a741 revert: macOS skip codesign 2025-12-03 17:51:24 +08:00
Slinetrac 56ad05cbf9 refactor(main): extract keyboard shortcuts to utils 2025-12-03 13:15:21 +08:00
Slinetrac 1727d0b6d4 refactor(preload): unify preload logic into preload.ts 2025-12-03 12:51:46 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> cbc8782bf6 chore(deps): update npm dependencies (#5701)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 12:23:50 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 76eb063f7d chore(deps): update cargo dependencies (#5699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 12:18:43 +08:00
Slinetrac edf6e7cc42 chore: improve i18n 2025-12-02 18:55:59 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 3a5ce1dd19 chore(deps): update dependency typescript-eslint to ^8.48.1 (#5698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 18:29:40 +08:00
Tunglies 550a7e0bb9 fix: restart core or change can not remember proxy select in Global mode with sidecar #5466 2025-12-02 16:25:25 +08:00
Slinetrac 1393828078 docs: Changelog.md 2025-12-02 11:23:00 +08:00
Slinetrac 32a5044026 fix(updater): compute real download progress and stabilize progress bar states 2025-12-02 11:06:10 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> bfa7e1a2e6 chore(deps): update dependency @eslint-react/eslint-plugin to ^2.3.10 (#5693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 10:36:14 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 9f151594ff chore(deps): update dependency i18next to ^25.7.1 (#5692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 09:36:19 +08:00
Tunglies a611f7d8a7 fix: switch reqwest client to use rustls-tls for improved security #5559 2025-12-01 22:22:26 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> d5f1af91f1 chore(deps): update dependency i18next to ^25.7.0 (#5691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 21:54:56 +08:00
Sline 008234988c fix: ensure early CSS injection by preloading Verge config (#5690)
* fix(theme): background css injection

* fix: ensure early CSS injection by preloading Verge config
2025-12-01 21:45:01 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 9056e8a298 chore(deps): update dependency vite to ^7.2.6 (#5682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 19:32:21 +08:00
Tunglies e6a4da8064 chore: update Mihomo(Meta) kernel version to v1.19.17 and improve warning formatting 2025-12-01 16:30:36 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 8f10f98db1 chore(deps): update dependency react-virtuoso to ^4.16.1 (#5675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 07:45:20 +08:00
Tunglies a1a9c2b7bb refactor: optimize reference handling in profile and i18n modules 2025-11-30 23:00:16 +08:00
Tunglies 6897ead070 perf: change patch_config parameter from Mapping to &Mapping for efficiency 2025-11-30 20:44:21 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> ae8c302bf4 chore(deps): update cargo dependencies (#5670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 19:48:44 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> aab8c8b3a1 chore(deps): update npm dependencies (#5671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 19:47:19 +08:00
Tunglies dca9b51b4b chore: macOS skip codesign 2025-11-30 16:16:16 +08:00
Tunglies a14f76eaf8 chore: comment out Apple signing credentials in dev workflow 2025-11-30 15:12:06 +08:00
Tunglies 3e4bdf2d64 chore: comment out Apple signing credentials in autobuild workflow 2025-11-30 14:38:31 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> b03dcd71f4 chore(deps): update dependency prettier to ^3.7.3 (#5663)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 08:48:14 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 25a3c2524f chore(deps): update github actions (#5660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-29 20:44:46 +08:00
Slinetrac 882f1a2a4a Revert "feat: imporve the layout of proxies page (#5647)"
This reverts commit 8c1caf8555.
2025-11-29 20:40:45 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> bd2d86fd8f chore(deps): update github actions (#5661)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-29 20:37:21 +08:00
Tunglies 271550205b chore: update autobuild action version, let renovate manage ci version 2025-11-29 20:28:41 +08:00
Slinetrac 45d4f0e89c chore: remove meta-json-schema #5656 #5478 2025-11-29 15:10:41 +08:00
Slinetrac 1d41cf691b fix: wake window correctly on first silent startup 2025-11-29 14:10:55 +08:00
DengHanjie 8c1caf8555 feat: imporve the layout of proxies page (#5647)
* feat: imporve the layout of proxies page to avoid navigator overlapping items (issue #5556)

* feat(proxy): remove left padding
2025-11-29 11:08:58 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 0fa77441f5 chore(deps): update npm dependencies (#5648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-29 10:04:40 +08:00
oomeow 6456c597ed perf: spawn clean task (#5595)
* perf: spawn task

* perf: spawn task to save draft date

* perf: store proxy client

* chore: update

* perf: reduce handle quit on macOS

* chore: clippy

* docs: update Changelog.md

* chore: update
2025-11-28 21:12:34 +08:00
Sline 683667f8ae fix(ui): prevent light flash on dark startup (#5637)
* fix(ui): prevent light flash on dark startup

* fix(window): apply initial dark/light theme to prevent white flash

* refactor(boot): optimize config fetch and theme fallback

* fix: system theme detection

* fix(window): remove black flash before loader by aligning initial paint with theme
2025-11-28 15:15:17 +08:00
Slinetrac 34aa26813f docs: Changelog.md 2025-11-28 11:05:49 +08:00
Slinetrac 99dda5496e fix(i18n): prevent zh flash and normalize language caching #5632 2025-11-28 10:38:42 +08:00
oomeow 9ce5d27d6e Revert "chore: frontend format check"
This reverts commit 90b850154d.
2025-11-27 21:50:25 +08:00
oomeow 90b850154d chore: frontend format check 2025-11-27 21:08:04 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> aed3060a11 chore(deps): update dependency prettier to ^3.7.1 (#5631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 20:46:33 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Slinetrac
3cd6e88c37 chore(deps): update dependency prettier to ^3.7.0 (#5625)
* chore(deps): update dependency prettier to ^3.7.0

* style: prettier

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-11-27 17:25:34 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> c2c628e8e0 chore(deps): update dependency @eslint-react/eslint-plugin to ^2.3.9 (#5613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 10:06:25 +08:00
Slinetrac a572708fd8 fix(search): prevent crash on invalid regex input in search box
Closes #5609
2025-11-26 18:40:22 +08:00
Slinetrac cbd8c89097 fix(connection-table): set boxSizing to border-box for header and cells
Closes #5611
2025-11-26 17:49:17 +08:00
Slinetrac affdfc045e fix(backup-config-viewer): add top margin to WebDAV address field to prevent floating label from overlapping dialog title
Closes #5608
2025-11-26 17:13:05 +08:00
Tunglies adb969d370 fix(ip-info-card): update IP detection to refresh only when necessary 2025-11-26 14:41:12 +08:00
Tunglies 22e2e751a2 refactor(profile): update get_profiles to return SharedBox and optimize clone usage 2025-11-26 11:27:02 +08:00
Tunglies 2e6e9a0db4 refactor(use_seq): optimize group handling by removing unnecessary cloning 2025-11-26 11:27:01 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 643e15b752 chore(deps): update dependency react-virtuoso to ^4.15.0 (#5600)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-26 11:10:52 +08:00
Tunglies ecc272aa20 feat: integrate tauri-plugin-clipboard-manager and add system info commands (#5593) 2025-11-25 16:58:25 +08:00
Slinetrac d3dc40e788 docs: Changelog.md 2025-11-25 11:51:00 +08:00
Sline b83ef994f8 refactor(traffic-monitor): Move traffic sampling/compression to a dedicated Web Worker (#5588)
* refactor(traffic-monitor): Move traffic sampling/compression to a dedicated Web Worker

* refactor(traffic-monitor): introduce shared sampler, safe fallback, and decoupled range handling

* refactor(traffic-monitor): add optional subscribe flag and fix range handling

* fix(traffic-monitor): age out idle samples and prevent ref-count updates after unmount
2025-11-25 11:43:45 +08:00
Sline caca8b2bc4 refactor: debugLog for frontend (#5587) 2025-11-25 10:01:04 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 64727ad660 chore(deps): update dependency typescript-eslint to ^8.48.0 (#5583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 08:49:32 +08:00
AetherWingandTunglies 6b3f5eea16 fix: correct flag emoji for ISO alpha-3 region code (#5557)
* fix: correct flag emoji for ISO alpha-3 region code

* fix: use rust_iso3166 to convert ISO3 region code

* fix: validate ISO country code when generating flag emoji

* fix: correct icon encoding for unlock test in specific regions

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-11-24 20:40:32 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 38b306a438 chore(deps): update dependency @types/react to v19.2.7 (#5577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 20:12:49 +08:00
Tunglies e24bb8f9cc chore: update cargo lock 2025-11-24 20:06:06 +08:00
Slinetrac 0c00a39754 docs: Changelog.md 2025-11-24 16:22:14 +08:00
Sline 8e8182f707 refactor(app-data): split monolithic context into focused SWR hooks (#5576)
* refactor(app-data): split monolithic context into focused SWR hooks

* refactor(swr): unify polling and consolidate proxy/config/provider data flow
2025-11-24 16:18:31 +08:00
Tunglies 871881c460 fix(resolve): implement resolve_done state management and refactor timer logic 2025-11-24 13:52:08 +08:00
Tunglies cbd1fa44d7 fix(reqwest): add native-tls feature to reqwest dependency #5559 2025-11-24 13:25:00 +08:00
Tunglies ad6fe696c0 fix: resolve proxy button and highlight state desynchronization; fix sidebar navigation issues 2025-11-23 20:01:54 +08:00
Tunglies 44adf55975 fix(proxy): sync system proxy switch state with background highlight #5240, #5439, #5421, #4651, #4536
Fixed race condition where button state and background color would desync
during rapid toggles or slow backend responses.

Root causes:
- Button used actualState while background used configState
- toggleSystemProxy returned immediately via setTimeout(0), releasing lock
  before backend operation completed
- 100ms delay in updateProxyStatus caused visual flash

Changes:
- Unified state source: both button and background now use actualState
- Made toggleSystemProxy truly async with proper await chain
- Wrapped toggle function with useLockFn to serialize operations
- Reduced state refresh delay from 100ms to 50ms
- Fixed GuardState lock release timing for async operations
- Added proper error rollback with state refresh on failure

Resolves issue where proxy toggle button would show as off while
background remained green, or vice versa.
2025-11-23 12:17:52 +08:00
Tunglies ddd24eb3ac fix: prevent drag listeners from blocking navigation clicks in sidebar #5567
Separated dnd-kit drag handlers (attributes/listeners) from ListItemButton
to ListItemIcon only. Previously, long-press or rapid clicks on nav items
would trigger drag detection and cancel the click event, causing the item
to visually highlight but fail to navigate.

Changes:
- Moved sortable attributes/listeners to icon elements only
- ListItemButton now handles pure navigation clicks without interference
- Drag handle cursor ('grab') remains on icons when sortable is enabled

Fixes issue where sidebar tabs would "go dark" without actual route change.
2025-11-23 11:47:27 +08:00
oomeowandTunglies cbab199f80 fix: failed to receive shutdown signal on windows (#5533)
* fix: receive shutdown signal failed on windows

* docs: update Changelog.md

* chore: update

* fix: use tokio runtime to handle shutdown signal

* docs: update Changelog.md

* fix: move tauri dependency to the correct section in Cargo.toml

* fix: remove unused exit handling code in run function

* fix(clash-verge-signal): use global runtime to avoid tokio runtime panic on unix

* chore: update tauri-plugin-mihomo deps

* fix: handle macOS exit event to ensure proper application termination

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-11-22 22:36:00 +08:00
Slinetrac 687530a9cd fix(sysproxy): remove redundant 172.29.0.0/16 entry #4351
Closes #4351
2025-11-22 19:05:57 +08:00
Sline 4ff186497c feat(tun): auto-redirect GUI support for linux (#5565)
* feat(tun): auto-redirect GUI support for linux

* refactor(tun-viewer): unify Linux-only auto-redirect row layout
2025-11-22 18:58:57 +08:00
Slinetrac 45020fceda refactor(proxies): reorder chain-mode toggle for immediate UI feedback 2025-11-22 17:06:35 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Slinetrac
51760a5b95 chore(deps): update npm dependencies (#5561)
* chore(deps): update npm dependencies

* fix: types

* chore: replace vite-plugin-monaco-editor-esm with utils/monaco

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-11-22 16:33:34 +08:00
Tunglies a271ba6ce5 feat: add update_proxy_chain_config method with detailed documentation for proxy and proxy-group configuration 2025-11-22 16:30:25 +08:00
Tunglies 82bed4910e perf: refactor IRuntime to use HashSet for exists_keys and improve related functions performance 2025-11-22 16:25:50 +08:00
Tunglies c82cefe80e feat: add clash-verge-types crate and integrate IRuntime struct 2025-11-22 15:33:24 +08:00
Slinetrac d110e462aa Revert "chore(deps): update npm dependencies (#5558)"
This reverts commit ddcd4a8192.
2025-11-22 13:49:17 +08:00
Slinetrac 1d9179ba7e Revert "fix: update vite-plugin-monaco-editor to version 1.1.0"
This reverts commit dd52522905.
2025-11-22 13:49:14 +08:00
Tunglies dd52522905 fix: update vite-plugin-monaco-editor to version 1.1.0 2025-11-22 12:01:34 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Tunglies
ddcd4a8192 chore(deps): update npm dependencies (#5558)
* chore(deps): update npm dependencies

* fix: correct monaco typescript reference in editor viewer

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-11-22 08:39:08 +08:00
Slinetrac c38dd5da14 fix(unlock): normalize and dedupe unlock items 2025-11-21 20:22:57 +08:00
Tunglies 803f749063 Revert "chore(deps): update npm dependencies (#5554)"
This reverts commit da0106b1e8.
2025-11-21 18:13:22 +08:00
Slinetrac 192b15a02d docs: CONTRIBUTING.md 2025-11-21 14:36:04 +08:00
Slinetrac a5a791a7f8 fix(ci): pnpm cache 2025-11-21 13:47:30 +08:00
Sline 02d0a7e61f chore(ci): add frontend-check.yml (#5555)
* chore(ci): add frontend-check.yml

* test

* Revert "test"

This reverts commit 24c4cf270c.
2025-11-21 13:28:54 +08:00
renovate[bot]renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Slinetrac
da0106b1e8 chore(deps): update npm dependencies (#5554)
* chore(deps): update npm dependencies

* fix: types

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-11-21 12:36:17 +08:00
Tunglies ee9e9182dc perf: update current_* methods to return references instead of owned Strings and decrease unnecessary arc clone 2025-11-21 12:17:35 +08:00
Tunglies 6a62009485 Revert "chore(deps): update dependency monaco-editor to ^0.55.1 (#5548)"
This reverts commit 22241f8f60.
2025-11-21 12:11:45 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 22241f8f60 chore(deps): update dependency monaco-editor to ^0.55.1 (#5548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 10:08:49 +08:00
Tunglies a1f8ab7308 refactor: update function signatures to use references for Mapping and String 2025-11-20 22:50:36 +08:00
Tunglies 5a677fbbeb refactor: update function signatures to use references for Mapping and String 2025-11-20 21:49:16 +08:00
Slinetrac fcc1d4f9b5 chore: remove vitest 2025-11-20 21:42:11 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> e3da884bde chore(deps): update dependency vitest to ^4.0.12 (#5547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 20:54:01 +08:00
Tunglies 8e3273a32c refactor: simplify sysproxy usage in network commands 2025-11-20 19:50:25 +08:00
Slinetrac 0827829cc5 docs: Changelog.md 2025-11-20 17:52:20 +08:00
Sline bf06cbc87d refactor: replace @mui/x-data-grid with @tanstack/react-table (#5544) 2025-11-20 17:41:34 +08:00
Tunglies 7e373ff39c fix(mihomo): unix can not connect mihomo ipc after reload config #5532 2025-11-20 17:02:57 +08:00
Tunglies 87e3125e67 fix: resolve issue with application not remembering user behavior on restart or shutdown 2025-11-20 16:07:05 +08:00
Tunglies 131cd6c3b8 fix(config): restart and quit application or shutdown system can not remember user's behavior #5347, #5359, #5430 2025-11-20 16:03:43 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> e42ae0e8c5 chore(deps): update dependency vite to ^7.2.4 (#5541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 15:54:09 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> c765016063 chore(deps): update dependency vite to ^7.2.3 (#5540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 15:48:52 +08:00
Slinetrac 614a2cf00b style: prettier 2025-11-20 15:27:42 +08:00
Slinetrac be97a27ba3 fix: Monaco syntax highlighting broken by CSS injection #5440 2025-11-20 15:16:25 +08:00
Tunglies b3dc48d07e fix(tests): suppress clippy expect warnings in enhance function
fix(timer): improve task removal logic in add_task method
refactor(notification): drop binding after emitting event
chore(Cargo): add lints section to Cargo.toml
2025-11-20 14:59:49 +08:00
Tunglies 4c8673fcb2 fix(timer): optimize lock handling in add_task method 2025-11-20 14:50:53 +08:00
Slinetrac 51a7b4fe75 docs: Changelog.md 2025-11-20 13:29:45 +08:00
Slinetrac 2bb9f648ba fix(app-data-provider): dashboard state sync 2025-11-20 13:23:50 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> c3fe2d31ef chore(deps): update npm dependencies (#5535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 12:35:17 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 4617ec0997 chore(deps): update dependency glob to v13 (#5534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 12:33:59 +08:00
Tunglies d8090277af refactor: replace singleton_lazy with singleton macro across multiple modules 2025-11-19 22:40:52 +08:00
Tunglies 108a05ce96 refactor: replace CommandChildGuard with CommandChild and simplify related methods 2025-11-19 22:11:22 +08:00
SlineandTunglies d808e59156 fix(enhance): prevent crash when deleted proxies remain in manual groups (#5522)
* feat(enhance): cleanup stale proxies and policies before final config

* feat(enhance): preserve provider-backed groups when sanitizing proxies

* docs: Changelog.md

* refactor: to_owned

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-11-19 18:59:20 +08:00
Tunglies ba3cd9b006 refactor: simplify IVerge struct by deriving Default and removing manual implementation 2025-11-19 18:40:53 +08:00
Tunglies aad5bb812c chore: add removeUnusedCommands to build configuration 2025-11-19 18:13:35 +08:00
Slinetrac 6ab597db8f fix: prevent filename from overlapping with action icons in backup records #5525
Closes #5525
2025-11-19 17:29:11 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 85302144b2 chore(deps): update npm dependencies (#5528)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 17:11:05 +08:00
Slinetrac ba90c6a1e1 docs: Changelog.md 2025-11-19 17:09:54 +08:00
Sline f439e93a2b feat(hotkey): add global reactivate_profiles shortcut (#5527)
* feat(hotkey): add global reactivate_profiles shortcut

* feat(profile): expose validation state for reactivation shortcuts
2025-11-19 17:06:23 +08:00
Slinetrac 94b07b51d6 chore: rename Youtube to YouTube
Closes #5526
2025-11-19 17:04:19 +08:00
oomeow 9e3bc6db8c chore: update tauri-plugin-mihomo deps 2025-11-18 22:02:14 +08:00
Tunglies 531307ebe1 fix(workflows): update pnpm cache key format and node version in autobuild and dev workflows 2025-11-18 20:05:49 +08:00
Tunglies df4c69e9cc fix(workflows): enable caching for all crates in autobuild, dev, and lint-clippy workflows 2025-11-18 19:55:30 +08:00
Tunglies 9ef4362b23 fix(autobuild): update file paths for deb, rpm, and nsis bundles 2025-11-18 19:40:34 +08:00
Tunglies 3ec7c6d2b8 feat(profiles): add profile preview structure and update profile menu handling 2025-11-18 19:08:07 +08:00
Slinetrac ec7f912eae fix(home-profile-card): use profiles label for home shortcut 2025-11-18 18:50:53 +08:00
Slinetrac e7812396df feat(logs): support reverse chronological order #5513
Closes #5513
2025-11-18 17:39:56 +08:00
Slinetrac 4fa8b1f118 chore(types): refine monaco plugin default export typing 2025-11-18 16:51:41 +08:00
Tunglies af9b1b777e feat(app_init): enhance IPC pool configuration with new connection limits and reject policy 2025-11-18 16:41:41 +08:00
Tunglies 096b1b0d81 fix(ci): update Rust cache configuration and artifact paths in dev workflow 2025-11-18 16:33:13 +08:00
Tunglies 7a81a673f3 chore(ci): update Rust cache configuration in autobuild and lint-clippy workflows for improved efficiency 2025-11-18 16:14:30 +08:00
Sline 38980b1249 chore(deps): bump pnpm10 (#5511)
* chore(deps): bump pnpm10

* fix: keep React and router in a single chunk to avoid runtime crash
2025-11-18 16:01:34 +08:00
Tunglies b34e4cf551 Revert "fix: update Rust cache keys in autobuild and lint-clippy workflows for consistency"
This reverts commit 9df0998f3e.
2025-11-18 15:53:04 +08:00
Tunglies 8339fabb17 feat(sysinfo): add tauri-plugin-clash-verge-sysinfo for system information retrieval (#5510)
* feat(sysinfo): add tauri-plugin-clash-verge-sysinfo for system information retrieval

* feat(sysinfo): add tauri-plugin-clash-verge-sysinfo for system information retrieval

* fix(service): import Manager trait for app handle in linux_running_as_root function
2025-11-18 15:48:48 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> ca35994ccb chore(deps): update npm dependencies (#5502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 13:33:45 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 92d3a59d0d chore(deps): update rust crate open to 5.3.3 (#5501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 13:32:18 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 4f6ceec0d5 chore(deps): update dependency glob to v12 (#5503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 13:31:17 +08:00
Tunglies 9df0998f3e fix: update Rust cache keys in autobuild and lint-clippy workflows for consistency 2025-11-18 10:11:32 +08:00
Tunglies e672d19622 feat(signal): add clash-verge-signal crate and integrate signal handling (#5500)
* feat(signal): add clash-verge-signal crate and integrate signal handling

* fix: clippy error on type complexity
2025-11-18 10:04:21 +08:00
16478d314b fix: install service (#5479)
* fix: compatible older service version when install service

* docs: update Changelog.md

* chore: check service available

* chore: wait for service server is running

* fix: update changelog to reflect compatibility with legacy service mode upgrades

---------

Co-authored-by: Tunglies <tunglies.dev@outlook.com>
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-11-18 09:52:48 +08:00
Slinetrac a5de24a545 fix(webdav): prevent dialog refresh loop and stabilize busy-state handling 2025-11-17 18:16:13 +08:00
Tunglies 35ae5cdba1 fix: update caching keys in workflow files to use glob pattern for Cargo.lock 2025-11-17 13:02:44 +08:00
Tunglies d1a099b480 fix: update file paths in release script for fixed WebView2 setup 2025-11-17 12:57:34 +08:00
Tunglies eb1768e212 fix: update file paths in autobuild script for WebView2 setup 2025-11-17 12:47:01 +08:00
Tunglies 056af768e5 feat: initialize workspace with clash-verge-draft and clash-verge-logging crates (#5489)
- Add Cargo.toml for workspace management, including dependencies and profiles.
- Create clash-verge-draft crate with basic structure, including a benchmark for Draft functionality.
- Implement Draft management with shared state and asynchronous modifications.
- Add tests for Draft functionality to ensure correctness.
- Create clash-verge-logging crate for logging utilities with structured log types and macros.
- Update src-tauri to use new crates and remove unnecessary configurations.
- Refactor existing code to utilize the new Draft and logging functionalities.
2025-11-17 11:51:50 +08:00
Tunglies 0866b93175 feat(logging): introduce clash-verge-logging crate for management (#5486)
* feat(logging): introduce clash-verge-logging crate for management

- Added a new crate `clash-verge-logging` with dependencies on `log`, `tokio`, `compact_str`, and `flexi_logger`.
- Implemented logging types and macros for structured logging across the application.
- Replaced existing logging imports with the new `clash_verge_logging` crate in various modules.
- Updated logging functionality to support different logging types and error handling.
- Refactored code to improve logging consistency and maintainability.

* fix(logging): update import paths for clash_verge_logging in linux.rs and dns.rs

* fix(logging): update import statement for clash_verge_logging in windows.rs
2025-11-17 10:42:57 +08:00
Slinetrac 635f63e9e5 feat: explicit paste button 2025-11-17 10:07:38 +08:00
Slinetrac f315a98afd fix: monaco editor line count limit 2025-11-17 07:41:37 +08:00
oomeow b0089d162b fix: dynamically modify pac content (#5468)
* fix: dynamically modify pac content

* docs: update Changelog.md
2025-11-16 18:53:43 +08:00
Slinetrac e1fc9547d8 docs: Changelog.md 2025-11-16 16:05:25 +08:00
Slinetrac cfd4532440 fix(home/traffic): ensure traffic chart stretches on Windows maximize 2025-11-16 15:42:14 +08:00
Tunglies 1eb4a0d834 fix(macos): remove tproxy-port from config on macOS #5439, #5397, #5372 2025-11-16 15:38:39 +08:00
Su Guoyu 29d8df40b9 fix(linux): retry with sudo when pkexec execution failed (#5469) 2025-11-16 14:52:13 +08:00
Tunglies dbb4877be6 refactor(Draft): management as crate (#5470)
* feat: implement draft functionality with apply and discard methods, and add benchmarks and tests

* Refactor Draft management and integrate Tokio for asynchronous operations

- Introduced a new `IVerge` struct for configuration management.
- Updated `Draft` struct to use `Arc<RwLock>` for better concurrency handling.
- Added asynchronous editing capabilities to `Draft` using Tokio.
- Replaced synchronous editing methods with asynchronous counterparts.
- Updated benchmark tests to reflect changes in the `Draft` API.
- Removed redundant draft utility module and integrated its functionality into the main `Draft` implementation.
- Adjusted tests to validate new behavior and ensure correctness of the `Draft` management flow.
2025-11-16 00:33:21 +08:00
Tunglies e66b8e7894 feat(timer): better subscription update timer after core startup 2025-11-15 21:18:35 +08:00
TungliesandSlinetrac 8654bad6b0 refactor: migrate proxy setting guard to sysproxy-rs crate (#5287)
* Refactor proxy management: Remove EventDrivenProxyManager and async_proxy_query

- Removed the EventDrivenProxyManager and its related event-driven proxy management logic.
- Replaced usages of AsyncProxyQuery with direct calls to sysproxy for fetching system and auto proxy configurations.
- Cleaned up constants by removing unused default proxy host.
- Updated sysopt to eliminate calls to the removed EventDrivenProxyManager.
- Adjusted logging and proxy state management to reflect the removal of event-driven architecture.
- Simplified the core module structure by removing unnecessary module imports.

* refactor: remove bypass module for cleaner network configuration

* feat: update sysproxy to version 0.4.0 and add guard feature; refactor sysopt for improved proxy management

* feat(windows/sysproxy): unify guard-type handling and auto-restore drift

* refactor(config): remove commented-out code for SysProxy update flag

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-11-15 20:51:36 +08:00
Slinetrac e021e45e9a fix(backup): correctly restore CSS injection from verge.yaml backups 2025-11-15 20:20:27 +08:00
Slinetrac 272c2f3467 Revert "fix(editor): restore syntax highlighting for JS/CSS in Monaco editor"
This reverts commit 55ac25b1a6.
vite-plugin-monaco-editor-esm has handled it
2025-11-15 16:46:11 +08:00
Slinetrac 0b641992e7 refactor(async-proxy): remove unnecessary Result wrappers from Windows registry helpers 2025-11-15 16:37:53 +08:00
Slinetrac 55ac25b1a6 fix(editor): restore syntax highlighting for JS/CSS in Monaco editor 2025-11-15 15:51:03 +08:00
Tunglies 40db928436 fix: suppress error from deep link registration in setup_plugins 2025-11-15 12:34:06 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 2eb5b5802d chore(deps): update dependency @types/react to v19.2.5 (#5465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 10:10:12 +08:00
Tunglies 7f2bced0d4 fix: remove unnecessary exit statement from pre-push script 2025-11-15 08:58:52 +08:00
Tunglies a6cb903fe6 refactor: simplify function return types and remove unnecessary wraps 2025-11-15 08:56:58 +08:00
Tunglies 2b0ba4dc95 refactor: streamline config handling and logging mechanisms 2025-11-15 02:46:34 +08:00
Tunglies f6f288f02b fix: prevent save yaml return too early to failed get runtime config 2025-11-15 01:09:22 +08:00
wonfen c4e558d377 refactor: replace isahc with reqwest (#5463) 2025-11-14 23:10:56 +08:00
Tunglies 8190432903 fix: update download links for Windows and macOS installers
I hate tauri-action update :(
2025-11-14 21:59:27 +08:00
oomeow 20cb3e49fd chore: don't record websocket logs 2025-11-14 20:23:25 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 4fb8611e55 chore(deps): update npm dependencies (#5462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 19:38:06 +08:00
Tunglies e41f910063 feat: add infoPlist configuration for macOS build 2025-11-14 17:30:27 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 4e800d165b chore(deps): update npm dependencies (#5455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 09:59:10 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> f1bd56ea13 chore(deps): update cargo dependencies (#5453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 23:17:55 +08:00
Slinetrac dbd46f2b76 fix: ruleEditor i18n keys 2025-11-13 21:45:04 +08:00
oomeow 18b3db46b0 chore: update tauri-plugin-mihomo deps 2025-11-13 20:24:24 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 0e2e919a58 chore(deps): update dependency @mui/x-data-grid to ^8.18.0 (#5452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 19:53:41 +08:00
oomeow 1125dc0562 feat: support closed connections (#5244)
* feat: support closed connections

* fix: clear closed connections

* feat: show footer

* feat: show closed connection detail

* docs: update Changelog.md

* chore: update
2025-11-13 19:50:23 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> d73036a3b6 chore(deps): update npm dependencies (#5441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 10:20:51 +08:00
Tunglies 9c24b3089c fix(editor): update editorRef initialization and handleChange logic for better type safety #5442 2025-11-13 09:42:03 +08:00
Tunglies d0d35da2f1 fix(editor): update theme mode for MonacoEditor to use 'light' instead of 'vs' in multiple components #5440 2025-11-13 08:39:10 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 3db69bd816 chore(deps): update npm dependencies (#5431)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 18:47:40 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 3fab4ce34a chore(deps): update dependency @types/react-dom to v19.2.3 (#5424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 14:13:43 +08:00
Sline 2a2de29224 feat: tray menu enhance (#5412)
* feat(tray): move outbound modes into a dedicated submenu

* refactor: Enable 'Show Proxy Groups Inline' by default

* refactor(tray): move `copyEnv` to `more` submenu
2025-11-12 14:10:20 +08:00
Slinetrac 2ea22feb95 fix(tray): refresh icon with latest draft before apply() commits 2025-11-12 11:41:28 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> a5f2b163f1 chore(deps): update dependency @types/node to ^24.10.1 (#5420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 10:55:48 +08:00
Tunglies f3a3d0e8b0 refactor: update update_icon method to require a non-optional verge reference and adjust related calls (#5417) 2025-11-12 07:36:24 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 92a3f0cae3 chore(deps): update dependency @types/react to v19.2.3 (#5416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 01:26:04 +08:00
Slinetrac d18c303e12 fix(tun): set linux DEFAULT_STACK to gvisor 2025-11-12 00:39:46 +08:00
Slinetrac b40136a790 chore: Changelog.md 2025-11-12 00:19:50 +08:00
Slinetrac 645b92bc28 fix(tun): remove Linux-only block that forced tun.stack to mixed 2025-11-12 00:08:17 +08:00
Sline afb3842776 feat(canvas): add adaptive FPS and pause rendering on window blur (#5409) 2025-11-11 20:15:07 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> e02f23e0aa chore(deps): update npm dependencies (#5400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 16:38:08 +08:00
Tunglies 6e9c6e60bb chore: update sysproxy-rs 2025-11-11 08:53:14 +08:00
Tunglies 600019f980 chore: update tauri-plugin-mihomo 2025-11-11 08:24:23 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 2d9eef834b chore(deps): update dependency @eslint-react/eslint-plugin to ^2.3.4 (#5394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 22:38:29 +08:00
Tunglies 33f575ed4d refactor: update icon handling in tray methods to accept IVerge reference and improve logging #5379 (#5387)
* refactor: update icon handling in tray methods to accept IVerge reference and improve logging #5379

* refactor: simplify icon retrieval logic in Tray implementation
2025-11-10 19:06:37 +08:00
Slinetrac 7d42d5cb0f feat: add proxy chain icon 2025-11-10 14:30:47 +08:00
Sline 838e401796 feat(auto-backup): implement centralized auto-backup manager and UI (#5374)
* feat(auto-backup): implement centralized auto-backup manager and UI

- Introduced AutoBackupManager to handle verge settings, run a background scheduler, debounce change-driven backups, and trim auto-labeled archives (keeps 20); wired into startup and config refresh hooks
  (src-tauri/src/module/auto_backup.rs:28-209, src-tauri/src/utils/resolve/mod.rs:64-136, src-tauri/src/feat/config.rs:102-238)

- Extended verge schema and backup helpers so scheduled/change-based settings persist, create_local_backup can rename archives, and profile/global-extend mutations now trigger backups
  (src-tauri/src/config/verge.rs:162-536, src/types/types.d.ts:857-859, src-tauri/src/feat/backup.rs:125-189, src-tauri/src/cmd/profile.rs:66-476, src-tauri/src/cmd/save_profile.rs:21-82)

- Added Auto Backup settings panel in backup dialog with dual toggles + interval selector; localized new strings across all locales
  (src/components/setting/mods/auto-backup-settings.tsx:1-138, src/components/setting/mods/backup-viewer.tsx:28-309, src/locales/en/settings.json:312-326 and mirrored entries)

- Regenerated typed i18n resources for strong typing in React
  (src/types/generated/i18n-keys.ts, src/types/generated/i18n-resources.ts)

* refactor(setting/backup): restructure backup dialog for consistent layout

* refactor(ui): unify settings dialog style

* fix(backup): only trigger auto-backup on valid saves & restore restarts app safely

* fix(backup): scrub console.log leak and rewire WebDAV dialog to actually probe server

* refactor: rename SubscriptionChange to ProfileChange

* chore: update i18n

* chore: WebDAV i18n improvements

* refactor(backup): error handling

* refactor(auto-backup): wrap scheduler startup with maybe_start_runner

* refactor: remove the redundant throw in handleExport

* feat(backup-history-viewer): improve WebDAV handling and UI fallback

* feat(auto-backup): trigger backups on all profile edits & improve interval input UX

* refactor: use InputAdornment

* docs: Changelog.md
2025-11-10 13:49:14 +08:00
Cactus 78d5cb5eca i18n: remove proxy chain icon due to design inconsistency (#5377)
style: prettier
2025-11-10 12:40:59 +08:00
Tunglies b308251022 refactor: replace async_runtime::block_on with AsyncHandler::block_on and add inline annotations 2025-11-10 09:30:55 +08:00
Tunglies 49e5a557ca refactor: add inline annotations to CommandChildGuard methods 2025-11-10 09:23:30 +08:00
Tunglies e66a4f6aca perf: some inline magic for Draft
This would improve 8-14% CPU for all API
2025-11-10 07:18:50 +08:00
Tunglies 204bfa36e5 refactor: reorganize imports and remove unused axios instance code 2025-11-10 02:12:16 +08:00
Slinetrac a8fe5a71f7 fix: use Manager as _ 2025-11-09 23:46:23 +08:00
Slinetrac c03e10b5dc fix: macOS clippy lint 2025-11-09 23:36:39 +08:00
Slinetrac 0dcdd7fed6 fix: clippy lint 2025-11-09 23:23:03 +08:00
Tunglies 221a1e89eb chore: update Changelog for v2.4.4 with Mihomo(Meta) kernel upgrade and additional improvements 2025-11-09 22:38:55 +08:00
Tunglies 4eeb883464 refactor: update imports to use as _ for unused identifiers across multiple files 2025-11-09 22:15:37 +08:00
Tunglies e6c8f762db refactor: replace cloned() with clone() for improved performance in multiple files 2025-11-09 22:07:12 +08:00
Tunglies 7f267fa727 fix: update configuration data access from latest_arc to data_arc in Clash and WebDAV commands and init windows 2025-11-09 21:26:11 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> f5160ef277 chore(deps): update dependency @tauri-apps/cli to v2.9.4 (#5375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-09 21:01:49 +08:00
Tunglies 5f2ac77923 chore: update Rust toolchain to version 1.91.0 in autobuild workflow
fix: update Rust toolchain action to use master branch for consistency

feat: add support for Windows ARM64 Gnullvm in development workflow

refactor: remove Windows ARM64 Gnullvm support and ensure Rust target is added for the pinned toolchain

feat: add Rust target installation step for pinned toolchain in autobuild workflow

fix: remove unnecessary blank lines in autobuild workflow
2025-11-09 20:53:19 +08:00
Tunglies 3e81dcd78f refactor: simplify icon file search logic in find_target_icons function
Try with 1.91.0 new API
2025-11-09 16:59:43 +08:00
Tunglies f644036e08 chore: update Rust toolchain to version 1.91 and adjust edition in rustfmt configuration 2025-11-09 16:31:37 +08:00
Tunglies c763fdd233 fix: update configuration data access from latest_arc to data_arc for TUN and system proxy settings 2025-11-09 16:05:32 +08:00
Tunglies 6026f432da fix: update method call to fetch configuration data for global hotkey settings 2025-11-09 16:02:21 +08:00
Tunglies d193460649 refactor: replace inline log fetching with dedicated script for better maintainability 2025-11-09 15:46:40 +08:00
Tunglies 02ff2d3c2e chore: update dependencies in Cargo.toml and Cargo.lock for improved performance and features 2025-11-09 15:35:22 +08:00
oomeow 72544ccb54 fix: missing app_log_max_size and app_log_max_count when saving misc settings 2025-11-09 13:32:59 +08:00
oomeow ba1b9796e8 fix: Tun disable failed due to too short timeout 2025-11-09 13:09:32 +08:00
SlinetracandLiMoon 2f7b3bb9a0 chore(i18n): update zhtw translations
Co-authored-by: LiMoon <azhe9335@gmail.com>
2025-11-09 10:51:21 +08:00
Slinetrac 5c323fc40f docs: move Changelog.history.md to docs 2025-11-09 10:15:21 +08:00
Tunglies d3d47d448a docs: update Changelog.md to include backend memory and performance optimizations 2025-11-09 02:52:22 +08:00
Tunglies 9c5cda793d refactor: streamline UI readiness handling by replacing RwLock with AtomicU8 and updating related functions 2025-11-09 02:47:48 +08:00
Tunglies 243f5b1b96 refactor: replace OnceCell with AtomicBool for UI readiness state 2025-11-09 02:29:50 +08:00
Tunglies a57ffba8ba refactor: replace RwLock with AtomicBool for is_exiting and emergency_mode in Handle and NotificationSystem 2025-11-09 02:23:35 +08:00
Tunglies 8f080389fe feat: add Changelog.md and update scripts to reference it
- Created a new Changelog.md file to document version updates and changes.
- Updated the updatelog.mjs script to parse Changelog.md instead of UPDATELOG.md.
- Modified updater-fixed-webview2.mjs to use the new Changelog.md for update notes.
2025-11-09 01:09:10 +08:00
Slinetrac 2a13696d06 docs: UPDATELOG.md 2025-11-09 00:21:03 +08:00
XiaoBuHalyandSlinetrac af4463fef1 feat(connections): enable persistent column visibility and ordering #5235 (#5249)
* feat(connections): enable persistent column visibility and ordering

fix(connections): smooth column manager access in table view

fix(connections): integrate column manager into column menu

- Move the Connections column manager trigger into the DataGrid column menu, reusing existing i18n strings
- Clean up the page toolbar by removing the standalone button and lifting dialog state to the parent page

fix(connections): unify column order handling and resolve lint warnings

fix(connections): unify column order handling and enforce 0 ESLint warnings

- extract reconcileColumnOrder helper and reuse it across:
  - initial normalization (useEffect)
  - manager drag order handler (handleManagerOrderChange)
- derive columnOptions directly from the computed, sorted columns to keep a single source of truth and prevent dialog/grid drift
- connections.tsx: remove direct setState in useEffect; gate dialog open with `isTableLayout && isColumnManagerOpen`; clean up unused imports
- connection-column-manager.tsx: replace deprecated `primaryTypographyProps` with `slotProps`
- run full lint; project now has 0 warnings on main configuration

* feat(connection-table): safeguard column visibility

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-11-09 00:15:01 +08:00
Slinerenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>rozan
c8aa72186e chore: i18n (#5276)
* chore: notice i18n

* feat: add script to clean up unused i18n keys

* chore: cleanup i18n keys

* refactor(i18n/proxies): migrate proxies UI to structured locale keys

* chore: i18n for rule module

* chore: i18n for profile module

* chore: i18n for connections module

* chore: i18n for settings module

* chore: i18n for verge settings

* chore: i18n for theme settings

* chore: i18n for theme

* chore(i18n): components.home.*

* chore(i18n): remove unused i18n keys

* chore(i18n): components.profile.*

* chore(i18n): components.connection

* chore(i18n): pages.logs.*

* chore(i18n): pages.*.provider

* chore(i18n): components.settings.externalCors.*

* chore(i18n): components.settings.clash.*

* chore(i18n): components.settings.liteMode.*

* chore(i18n): components.settings.backup.*

* chore(i18n): components.settings.clash.port.*

* chore(i18n): components.settings.misc.*

* chore(i18n): components.settings.update.*

* chore(i18n): components.settings.sysproxy.*

* chore(i18n): components.settings.sysproxy.*

* chore(i18n): pages.profiles.notices/components.providers.notices

* refactor(notice): unify showNotice usage

* refactor(notice): add typed showNotice shortcuts, centralize defaults, and simplify subscriptions

* refactor: unify showNotice usage

* refactor(notice): unify showNotice API

* refactor(notice): unify showNotice usage

* chore(i18n): components.test.*

* chore(i18n): components.settings.dns.*

* chore(i18n): components.home.clashInfo.*

* chore(i18n): components.home.systemInfo.*

* chore(i18n): components.home.ipInfo/traffic.*

* chore(i18n): navigation.*

* refactor(i18n): remove pages.* namespace and migrate route texts under module-level page keys

* chore(i18n): common.*

* chore(i18n): common.*

* fix: change error handling in patch_profiles_config to return false when a switch is in progress

* fix: improve error handling in patch_profiles_config to prevent requests during profile switching

* fix: change error handling in patch_profiles_config to return false when a switch is in progress

fix: ensure CURRENT_SWITCHING_PROFILE is reset after config updates in perform_config_update and patch_profiles_config

* chore(i18n): restructure root-level locale keys into namespaces

* chore(i18n): add missing i18n keys

* docs: i18n guide

* chore: adjust i18n

* refactor(i18n): align UI actions and status labels with common keys

* refactor(i18n): unify two-name locale namespaces

* refactor(i18n/components): unify locale keys and update component references

* chore(i18n): add shared and entities namespaces to all locale files

* refactor(i18n): consolidate shared and entity namespaces across features

* chore(deps): update npm dependencies to ^7.3.5 (#5310)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* refactor(i18n): migrate shared editor modes and consolidate entities namespaces

* tmp

* refactor(i18n): flatten locales and move theme/validation strings

* docs: CONTRIBUTING_i18n.md

* refactor(i18n): restructure feedback and profile namespaces for better organization

* refactor(i18n): unify settings locale structure and update references

* refactor(i18n): reorganize locale keys for home, proxies, rules, connections, logs, unlock, and tests

* refactor(i18n/feedback/layout): unify shared toasts & normalize layout namespace

* refactor(i18n): centralize common UI strings in shared

* refactor(i18n): flatten headers and unify locale schema

* refactor(i18n): consolidate duplicate per-feature translations into shared namespace

* refactor(i18n): split locales into per-namespace files

* style: lint

* refactor(i18n): unify unlock UI translations under tests namespace

* feat(i18n): add type-checked translation keys

* style: eslint import order

* feat(i18n): replace ad-hoc loader with rust-i18n backend bundles

* chore(prebuild): remove locale-copy step

* fix(i18n, notice): propagate runtime params and update cleanup script path

* fix(i18n,notice): make locale formatting idempotent and guard early notice translations

* fix(i18n): resolve locale aliases and match OS codes correctly

* fix(unlock): use i18next-compatible double-brace interpolation in failure notice

* fix(i18n): route unlock error notices through translation keys

* fix(i18n): i18n types

* feat(i18n): localize upgrade notice for Clash core viewer

* fix(notice): ensure runtime overrides apply to prefix translations

* chore(i18n): replace literal notices with translation keys

* chore(i18n): types

* chore(i18n): regen typings before formatting to keep keys in sync

* chore(i18n): simply labels

* chore(i18n): adjust translation

* chore: remove eslint-plugin-i18next

* chore(i18n): add/refine Korean translations across frontend scopes and Rust backend (#5341)

* chore(i18n): translate settings.json (missed in previous pass) (#5343)

* chore(i18n): add/refine Korean translations across frontend scopes and Rust backend

* chore(i18n): add/refine Korean translations across frontend scopes and Rust backend

* fix(i18n-tauri): quote placeholder-leading value in ko.yml to prevent rust_i18n parse panic

* chore(i18n): translate settings.json (forgot to include previously)

---------

Co-authored-by: rozan <34974262+thelojan@users.noreply.github.com>
2025-11-08 19:40:38 +08:00
Tunglies c1a2e99f63 feat: migrate from react-monaco-editor to @monaco-editor/react and update editor components (#5263)
* feat: migrate from react-monaco-editor to @monaco-editor/react and update editor components

* chore: add acknowledgments for contributors in update log

* feat: enhance frontend information editing component for improved performance
2025-11-08 19:32:26 +08:00
Tunglies 9d874e5c6f chore: update version to 2.4.4 in package.json, Cargo.toml, Cargo.lock, tauri.conf.json, and add new changelog section 2025-11-08 19:16:51 +08:00
Tunglies fa74e79f40 fix: update version from 2.4.3-rc to 2.4.3 in package.json, Cargo.toml, Cargo.lock, and tauri.conf.json 2025-11-08 19:10:29 +08:00
Tunglies 6017423a49 chore: update version to 2.4.3-rc and enhance release notes 2025-11-08 18:31:22 +08:00
Tunglies 538cba5a33 fix: return correct type in get_profiles function 2025-11-08 17:07:48 +08:00
oomeow 76bf91ff8a fix: import profile panic (#5346)
* fix: import profile panic

docs: update UPDATELOG.md

chore: update

* perf: spawn import profile
2025-11-07 22:46:30 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 758c21e656 chore(deps): update dependency vitest to ^4.0.8 (#5349)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 21:51:09 +08:00
Slinetrac 5759bed687 refactor(linux): replace users crate with libc::geteuid for root detection
Closes #5333
2025-11-07 18:58:13 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 9cc2f44379 chore(deps): update dependency vite to ^7.2.2 (#5342)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 16:22:28 +08:00
rozanandSlinetrac aa6a1c8650 add Korean README and update language selectors (#5338)
* docs(i18n): add Korean README and update language selectors

* style: prettier

---------

Co-authored-by: Slinetrac <realakayuki@gmail.com>
2025-11-07 12:06:48 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 5b3dbaaec6 chore(deps): update dependency i18next to ^25.6.1 (#5331)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 10:49:11 +08:00
oomeow 81e35e2ead chore: optimize (#5319)
* chore: optimize and reduce spawn

* chore: optimize
2025-11-07 06:06:09 +08:00
Tunglies 19b3899a1b refactor: improve notification handling and avoid backend loop with empty messages 2025-11-07 00:54:16 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 333440535b chore(deps): update dependency vite to ^7.2.1 (#5324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 14:17:21 +08:00
Tunglies 409b16b49f refactor: streamline admin check logic and improve get_running_mode return type (#5325) 2025-11-06 13:37:11 +08:00
Tunglies 9a1465ec4d refactor: replace AtomicI64 with Instant for app start time tracking and simplify uptime calculation 2025-11-06 12:59:05 +08:00
Tunglies 04fc9640c6 refactor: move handle_window_destroyed function under macOS conditional compilation 2025-11-06 11:31:15 +08:00
Slinetrac 966693c9cb fix: clippy lint 2025-11-06 11:21:12 +08:00
Tunglies 363fa98891 refactor: enhance YouTube Premium check logic and streamline response handling 2025-11-06 11:03:35 +08:00
Tunglies a0f7fb7952 refactor: update linting rules and optimize directory handling in Unix 2025-11-06 10:52:14 +08:00
Tunglies 5a8e83cd49 refactor: change function definitions to const for improved performance and clarity 2025-11-06 10:47:25 +08:00
Tunglies aaf3ebe547 refactor: enhance error handling in Timer and update server argument collection 2025-11-06 10:42:21 +08:00
Tunglies 651513c826 refactor: optimize error message handling and improve cloning in various functions 2025-11-06 10:26:40 +08:00
Tunglies 69a706b438 refactor: streamline error handling and resource management in various modules 2025-11-06 10:19:06 +08:00
Tunglies 671ac2ebed refactor: replace type references with Self in various structs and methods for consistency 2025-11-06 10:19:06 +08:00
Tunglies 70236f781c refactor: simplify uid assignment in PrfItem from_merge and from_script methods 2025-11-06 10:19:05 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> ccffc0a4fa chore(deps): update npm dependencies (#5321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 09:36:30 +08:00
Tunglies 21de5cbb0b refactor: remove unnecessary emit calls in switch_proxy_node and enhance profile switch notifications 2025-11-06 00:30:43 +08:00
Tunglies 6eaf999240 refactor: optimize worker loop to use try_recv for non-blocking event processing #5318 2025-11-05 22:59:22 +08:00
Tunglies 1bd2bde096 refactor: streamline lightweight mode initialization and logging levels 2025-11-05 18:57:04 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> cf89ac381a chore(deps): update npm dependencies (#5317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 18:47:43 +08:00
Tunglies ea41e71f72 refactor: replace EncryptionGuard with with_encryption for better async context handling 2025-11-05 16:56:04 +08:00
Tunglies 8c3e9c9ea9 feat: implement encryption guard for secure data handling in YAML operations 2025-11-05 15:05:19 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 7145096729 chore(deps): update npm dependencies (#5311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 12:13:07 +08:00
Tunglies 3e2f605e77 fix: improve error handling and logging in various modules 2025-11-05 02:11:43 +08:00
Tunglies 28483ff9db fix: simplify error handling in toggle_proxy_profile by logging errors directly 2025-11-04 23:05:03 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 5c207a8a7d chore(deps): update npm dependencies to ^7.3.5 (#5310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 23:05:03 +08:00
Tunglies ab136e463f fix: change error handling in patch_profiles_config to return false when a switch is in progress
fix: improve error handling in patch_profiles_config to prevent requests during profile switching

fix: change error handling in patch_profiles_config to return false when a switch is in progress

fix: ensure CURRENT_SWITCHING_PROFILE is reset after config updates in perform_config_update and patch_profiles_config
2025-11-04 23:05:01 +08:00
Tunglies 97769cf93a feat: add methods to retrieve current subscription details in PrfItem and refactor profile handling in IProfiles 2025-11-04 12:03:03 +08:00
Tunglies 0306f73841 fix: replace update_tray_display with update_menu for consistency across tray updates 2025-11-04 12:03:03 +08:00
Tunglies 0a03867352 fix: optimize ownership handling in patch macro for better performance 2025-11-04 12:03:03 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 28a78464e1 chore(deps): update npm dependencies (#5303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 12:00:47 +08:00
Tunglies bae584b1ab perf: optimize profile handle memory usage 2025-11-04 10:05:17 +08:00
Tunglies a5e6a35dea fix: update IVerge struct to skip serializing optional fields and adjust types for latency timeout and proxy layout column 2025-11-04 08:16:02 +08:00
Tunglies b86ceb26f6 fix: streamline verge configuration fetching and patching functions 2025-11-04 08:01:33 +08:00
Tunglies b70d45b66a fix: update profile handling to apply and discard changes correctly 2025-11-04 07:58:14 +08:00
Tunglies ebd7f457d2 fix: improve error handling in port settings save function #4707 2025-11-04 07:17:14 +08:00
Tunglies 72d94c62f4 Revert "modify the port detection range to prevent the use of system ports (#3587)"
This reverts commit d3dbc11b1b.
2025-11-04 07:11:44 +08:00
Tunglies fe757ed984 docs(autobuild): Assets URL correction for rpm package 2025-11-04 06:23:53 +08:00
Tunglies 2287ea5f0b Refactor configuration access to use latest_arc() instead of latest_ref()
- Updated multiple instances in the codebase to replace calls to latest_ref() with latest_arc() for improved performance and memory management.
- This change affects various modules including validate, enhance, feat (backup, clash, config, profile, proxy, window), utils (draft, i18n, init, network, resolve, server).
- Ensured that all references to configuration data are now using the new arc-based approach to enhance concurrency and reduce cloning overhead.

refactor: update imports to explicitly include ClashInfo and Config in command files
2025-11-04 06:06:20 +08:00
Slinetrac 48a19f99e2 docs: UPDATELOG.md 2025-11-03 20:54:46 +08:00
❤是纱雾酱哟~ 1003118468 docs(release): Assets URL correction (#5294)
- Fix mis-spelling of assets URL to Linux RPM package
2025-11-03 20:45:06 +08:00
renovate[bot]andrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 940717712a chore(deps): update dependency @types/node to ^24.10.0 (#5292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 20:44:58 +08:00
536 changed files with 45334 additions and 26825 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
+18 -18
View File
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Check tag and package.json version
id: check_tag
@@ -64,7 +64,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Delete Old Alpha Tags Except Latest
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -180,23 +180,23 @@ jobs:
needs: delete_old_assets
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -f "Changelog.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' Changelog.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
echo "No update sections found in Changelog.md"
fi
else
echo "UPDATELOG.md file not found"
echo "Changelog.md file not found"
fi
shell: bash
@@ -275,7 +275,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
@@ -305,9 +305,9 @@ jobs:
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -360,7 +360,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
@@ -375,9 +375,9 @@ jobs:
save-if: false
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
@@ -491,7 +491,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -503,9 +503,9 @@ jobs:
save-if: false
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -522,8 +522,8 @@ jobs:
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 2
+131 -116
View File
@@ -31,35 +31,22 @@ jobs:
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
run: bash ./scripts/extract_update_logs.sh
shell: bash
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v4.2.0
name: Install pnpm
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -90,20 +77,20 @@ jobs:
### Windows (不再支持Win7)
#### 正常版本(推荐)
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe)
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe)
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
### macOS
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64_darwin.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_darwin.dmg)
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64.dmg)
### Linux
#### DEB包(Debian系) 使用 apt ./路径 安装
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
#### RPM包(Redhat系) 使用 dnf ./路径 安装
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.armhfp.rpm)
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
### FAQ
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
@@ -122,7 +109,7 @@ jobs:
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
generate_release_notes: false
clean_old_assets:
name: Clean Old Release Assets
@@ -150,15 +137,18 @@ jobs:
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.91.0"
targets: ${{ matrix.target }}
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -166,17 +156,28 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
save-if: ${{ github.ref == 'refs/heads/dev' }}
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
if: matrix.os == 'ubuntu-24.04'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
sudo apt install \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
- name: Install x86 OpenSSL (macOS only)
if: matrix.target == 'x86_64-apple-darwin'
@@ -187,24 +188,24 @@ jobs:
echo "OPENSSL_LIB_DIR=$(brew --prefix openssl@3)/lib" >> $GITHUB_ENV
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v4.2.0
name: Install pnpm
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
cache: "pnpm"
- name: Cache pnpm store
uses: actions/cache@v4
- name: Pnpm Cache
uses: actions/cache@v5
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
restore-keys: |
${{ runner.os }}-pnpm-
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
- name: Pnpm install and check
run: |
@@ -214,6 +215,13 @@ jobs:
- name: Release ${{ env.TAG_CHANNEL }} Version
run: pnpm release-version autobuild-latest
- name: Add Rust Target
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Tauri build for Windows-macOS-Linux
uses: tauri-apps/tauri-action@v0
env:
@@ -245,19 +253,22 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
target: aarch64-unknown-linux-gnu
arch: arm64
- os: ubuntu-22.04
- os: ubuntu-24.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.91.0"
targets: ${{ matrix.target }}
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -265,30 +276,32 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
save-if: ${{ github.ref == 'refs/heads/dev' }}
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.2.0
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
cache: "pnpm"
- name: Cache pnpm store
uses: actions/cache@v4
- name: Pnpm Cache
uses: actions/cache@v5
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
restore-keys: |
${{ runner.os }}-pnpm-
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
- name: Pnpm install and check
run: |
@@ -300,38 +313,37 @@ jobs:
- name: Setup for linux
run: |
sudo ls -lR /etc/apt/
sudo dpkg --add-architecture ${{ matrix.arch }}
cat > /tmp/sources.list << EOF
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
sudo rm -f /etc/apt/sources.list.d/ubuntu.sources
sudo tee /etc/apt/sources.list << EOF
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-security main restricted universe multiverse
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu noble-backports main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
deb [arch=${{ matrix.arch }}] http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt-get update -y
sudo apt-get -f install -y
sudo apt-get install -y libglib2.0-dev-bin
sudo apt-get install -y \
linux-libc-dev:${{ matrix.arch }} \
libc6-dev:${{ matrix.arch }}
libc6-dev:${{ matrix.arch }} \
libicu-dev:${{ matrix.arch }}
sudo apt-get install -y \
libxslt1.1:${{ matrix.arch }} \
sudo apt-get install -y --no-install-recommends \
libxslt1-dev:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
librsvg2-dev:${{ matrix.arch }} \
patchelf
- name: Install aarch64 tools
if: matrix.target == 'aarch64-unknown-linux-gnu'
@@ -347,6 +359,13 @@ jobs:
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Add Rust Target
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Tauri Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
@@ -378,8 +397,8 @@ jobs:
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
target/${{ matrix.target }}/release/bundle/deb/*.deb
target/${{ matrix.target }}/release/bundle/rpm/*.rpm
autobuild-x86-arm-windows_webview2:
name: Autobuild x86 and ARM Windows with WebView2
@@ -398,7 +417,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -406,30 +425,32 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
save-if: ${{ github.ref == 'refs/heads/dev' }}
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.2.0
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
cache: "pnpm"
- name: Cache pnpm store
uses: actions/cache@v4
- name: Pnpm Cache
uses: actions/cache@v5
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
restore-keys: |
${{ runner.os }}-pnpm-
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
- name: Pnpm install and check
run: |
@@ -441,11 +462,18 @@ jobs:
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Add Rust Target
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Tauri build for Windows
id: build
uses: tauri-apps/tauri-action@v0
@@ -461,19 +489,19 @@ jobs:
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
$files = Get-ChildItem ".\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
$files = Get-ChildItem ".\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
$files = Get-ChildItem ".\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
@@ -486,7 +514,7 @@ jobs:
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
files: target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --${{ env.TAG_NAME }}
@@ -505,32 +533,19 @@ jobs:
]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
run: bash ./scripts/extract_update_logs.sh
shell: bash
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v4.2.0
name: Install pnpm
with:
run_install: false
@@ -565,20 +580,20 @@ jobs:
### Windows (不再支持Win7)
#### 正常版本(推荐)
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe)
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe)
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
### macOS
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64_darwin.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_darwin.dmg)
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64.dmg)
### Linux
#### DEB包(Debian系) 使用 apt ./路径 安装
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
#### RPM包(Redhat系) 使用 dnf ./路径 安装
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}-1.armhfp.rpm)
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
### FAQ
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
@@ -53,7 +53,7 @@ jobs:
autobuild_version: ${{ steps.check.outputs.autobuild_version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 50
+3 -3
View File
@@ -42,7 +42,7 @@ jobs:
autobuild_version: ${{ steps.check.outputs.autobuild_version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 50
@@ -56,7 +56,7 @@ jobs:
echo "🔍 Finding last commit with Tauri-related changes..."
# Define patterns for Tauri-related files
TAURI_PATTERNS="src/ src-tauri/src src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.*.conf.json src-tauri/build.rs src-tauri/capabilities"
TAURI_PATTERNS="src/ src-tauri/src src-tauri/Cargo.toml Cargo.lock src-tauri/tauri.*.conf.json src-tauri/build.rs src-tauri/capabilities"
# Get the last commit that changed any of these patterns (excluding build artifacts)
LAST_TAURI_COMMIT=""
@@ -105,7 +105,7 @@ jobs:
needs: check_current_version
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Clean old assets from release
env:
+3 -3
View File
@@ -30,7 +30,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
@@ -41,9 +41,9 @@ jobs:
run: rustup target add ${{ matrix.target }}
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "20"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
+33 -18
View File
@@ -70,24 +70,22 @@ jobs:
- name: Checkout Repository
if: github.event.inputs[matrix.input] == 'true'
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
if: github.event.inputs[matrix.input] == 'true'
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
if: github.event.inputs[matrix.input] == 'true'
run: rustup target add ${{ matrix.target }}
uses: dtolnay/rust-toolchain@1.91.0
- name: Rust Cache
if: github.event.inputs[matrix.input] == 'true'
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
save-if: ${{ github.ref == 'refs/heads/dev' }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
shared-key: autobuild-shared
cache-workspace-crates: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04' && github.event.inputs[matrix.input] == 'true'
@@ -103,11 +101,20 @@ jobs:
- name: Install Node
if: github.event.inputs[matrix.input] == 'true'
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "20"
node-version: "24.13.0"
cache: "pnpm"
- name: Pnpm Cache
uses: actions/cache@v5
with:
path: ~/.pnpm-store
key: "pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
restore-keys: |
pnpm-shared-stable-${{ matrix.os }}-${{ matrix.target }}
lookup-only: true
- name: Pnpm install and check
if: github.event.inputs[matrix.input] == 'true'
run: |
@@ -118,6 +125,14 @@ jobs:
if: github.event.inputs[matrix.input] == 'true'
run: pnpm release-version ${{ env.TAG_NAME }}
- name: Add Rust Target
if: github.event.inputs[matrix.input] == 'true'
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Tauri build
if: github.event.inputs[matrix.input] == 'true'
uses: tauri-apps/tauri-action@v0
@@ -138,24 +153,24 @@ jobs:
- name: Upload Artifacts (macOS)
if: matrix.os == 'macos-latest' && github.event.inputs[matrix.input] == 'true'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
path: target/${{ matrix.target }}/release/bundle/dmg/*.dmg
if-no-files-found: error
- name: Upload Artifacts (Windows)
if: matrix.os == 'windows-latest' && github.event.inputs[matrix.input] == 'true'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe
path: target/${{ matrix.target }}/release/bundle/nsis/*.exe
if-no-files-found: error
- name: Upload Artifacts (Linux)
if: matrix.os == 'ubuntu-22.04' && github.event.inputs[matrix.input] == 'true'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
path: target/${{ matrix.target }}/release/bundle/deb/*.deb
if-no-files-found: error
+75
View File
@@ -0,0 +1,75 @@
name: Frontend Check
on:
pull_request:
workflow_dispatch:
env:
HUSKY: 0
jobs:
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check frontend changes
id: check_frontend
uses: dorny/paths-filter@v3
with:
filters: |
frontend:
- 'src/**'
- '**/*.js'
- '**/*.ts'
- '**/*.tsx'
- '**/*.css'
- '**/*.scss'
- '**/*.json'
- '**/*.md'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'eslint.config.ts'
- 'tsconfig.json'
- 'vite.config.*'
- name: Skip if no frontend changes
if: steps.check_frontend.outputs.frontend != 'true'
run: echo "No frontend changes, skipping frontend checks."
- name: Install pnpm
if: steps.check_frontend.outputs.frontend == 'true'
uses: pnpm/action-setup@v4
with:
run_install: false
- uses: actions/setup-node@v6
if: steps.check_frontend.outputs.frontend == 'true'
with:
node-version: "24.13.0"
cache: "pnpm"
- name: Restore pnpm cache
if: steps.check_frontend.outputs.frontend == 'true'
uses: actions/cache@v5
with:
path: ~/.pnpm-store
key: "pnpm-shared-stable-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
restore-keys: |
pnpm-shared-stable-${{ runner.os }}-
- run: pnpm install --frozen-lockfile
if: steps.check_frontend.outputs.frontend == 'true'
- name: Run Prettier check
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm format:check
- name: Run ESLint
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm lint
- name: Run TypeScript typecheck
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm typecheck
+7 -5
View File
@@ -44,7 +44,7 @@ jobs:
echo "Manual trigger detected: skipping changes check and running clippy."
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@master
@@ -58,11 +58,13 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: ${{ github.ref == 'refs/heads/dev' }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
save-if: false
shared-key: autobuild-${{ runner.os }}-${{ matrix.target }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/Cargo.lock') }}
cache-workspace-crates: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
+101 -69
View File
@@ -5,6 +5,7 @@ on:
# ! 不再使用 workflow_dispatch 触发。
# workflow_dispatch:
push:
# -rc tag 时预览发布, 跳过 telegram 通知、跳过 winget 提交、跳过 latest.json 文件更新
tags:
- "v*.*.*"
permissions: write-all
@@ -23,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -68,24 +69,11 @@ jobs:
needs: [release, release-for-linux-arm, release-for-fixed-webview2]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
run: bash ./scripts/extract_update_logs.sh
shell: bash
- name: Set Env
@@ -133,6 +121,7 @@ jobs:
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
@@ -143,7 +132,7 @@ jobs:
name: "Clash Verge Rev ${{ env.TAG_NAME }}"
body_path: release.txt
draft: false
prerelease: false
prerelease: ${{ contains(github.ref_name, '-rc') }}
token: ${{ secrets.GITHUB_TOKEN }}
# generate_release_notes: true
@@ -168,10 +157,13 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.91.0"
targets: ${{ matrix.target }}
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -179,8 +171,13 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
save-if: ${{ github.ref == 'refs/heads/dev' }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
@@ -198,9 +195,9 @@ jobs:
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -212,8 +209,16 @@ jobs:
pnpm i
pnpm run prebuild ${{ matrix.target }}
- name: Add Rust Target
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Tauri build
uses: tauri-apps/tauri-action@v0
# 上游 5.24 修改了 latest.json 的生成逻辑,且依赖 tauri-plugin-update 2.10.0 暂未发布,故锁定在 0.5.23 版本
uses: tauri-apps/tauri-action@v0.6.1
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -230,7 +235,7 @@ jobs:
releaseName: "Clash Verge Rev ${{ github.ref_name }}"
releaseBody: "Draft release, will be updated later."
releaseDraft: true
prerelease: false
prerelease: ${{ contains(github.ref_name, '-rc') }}
tauriScript: pnpm
args: --target ${{ matrix.target }}
includeUpdaterJson: true
@@ -251,10 +256,13 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.91.0"
targets: ${{ matrix.target }}
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -262,13 +270,18 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
save-if: ${{ github.ref == 'refs/heads/dev' }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
@@ -324,6 +337,13 @@ jobs:
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Add Rust Target
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
@@ -354,9 +374,10 @@ jobs:
name: "Clash Verge Rev v${{env.VERSION}}"
body: "See release notes for detailed changelog."
token: ${{ secrets.GITHUB_TOKEN }}
prerelease: ${{ contains(github.ref_name, '-rc') }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
target/${{ matrix.target }}/release/bundle/deb/*.deb
target/${{ matrix.target }}/release/bundle/rpm/*.rpm
release-for-fixed-webview2:
name: Release Build for Fixed WebView2
@@ -374,7 +395,13 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.91.0"
targets: ${{ matrix.target }}
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
@@ -382,13 +409,18 @@ jobs:
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
save-if: ${{ github.ref == 'refs/heads/dev' }}
prefix-key: "v1-rust"
key: "rust-shared-stable-${{ matrix.os }}-${{ matrix.target }}"
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -402,14 +434,21 @@ jobs:
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Add Rust Target
run: |
# Ensure cross target is installed for the pinned toolchain; fallback without explicit toolchain if needed
rustup target add ${{ matrix.target }} --toolchain 1.91.0 || rustup target add ${{ matrix.target }}
rustup target list --installed
echo "Rust target ${{ matrix.target }} installed."
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
uses: tauri-apps/tauri-action@v0.6.1
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -421,19 +460,19 @@ jobs:
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
$files = Get-ChildItem ".\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
$files = Get-ChildItem ".\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
$files = Get-ChildItem ".\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
@@ -446,7 +485,8 @@ jobs:
name: "Clash Verge Rev v${{steps.build.outputs.appVersion}}"
body: "See release notes for detailed changelog."
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
prerelease: ${{ contains(github.ref_name, '-rc') }}
files: target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }}
@@ -454,17 +494,18 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update:
if: ${{ !contains(github.ref_name, '-rc') }}
name: Release Update
runs-on: ubuntu-latest
needs: [update_tag]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -480,16 +521,17 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update-for-fixed-webview2:
if: ${{ !contains(github.ref_name, '-rc') }}
runs-on: ubuntu-latest
needs: [update_tag]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -505,12 +547,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
submit-to-winget:
if: ${{ !contains(github.ref_name, '-rc') }}
name: Submit to Winget
runs-on: ubuntu-latest
needs: [update_tag, release-update]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get Version
@@ -528,6 +571,7 @@ jobs:
token: ${{ secrets.WINGET_TOKEN }}
notify-telegram:
if: ${{ !contains(github.ref_name, '-rc') }}
name: Notify Telegram
runs-on: ubuntu-latest
needs:
@@ -539,30 +583,17 @@ jobs:
]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
run: bash ./scripts/extract_update_logs.sh
shell: bash
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -616,6 +647,7 @@ jobs:
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
@@ -14,7 +14,7 @@ jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Check Rust changes
id: check_rust
@@ -39,42 +39,6 @@ jobs:
if: steps.check_rust.outputs.rust == 'true'
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Web changes
id: check_web
uses: dorny/paths-filter@v3
with:
filters: |
web:
- 'src/**'
- '**/*.js'
- '**/*.ts'
- '**/*.tsx'
- '**/*.css'
- '**/*.scss'
- '**/*.json'
- '**/*.md'
- '**/*.json'
- name: Skip if no Web changes
if: steps.check_web.outputs.web != 'true'
run: echo "No web changes, skipping prettier."
- uses: actions/setup-node@v4
if: steps.check_web.outputs.web == 'true'
with:
node-version: "lts/*"
- run: corepack enable
if: steps.check_web.outputs.web == 'true'
- run: pnpm install --frozen-lockfile
if: steps.check_web.outputs.web == 'true'
- run: pnpm format:check
if: steps.check_web.outputs.web == 'true'
# taplo:
# name: taplo (.toml files)
# runs-on: ubuntu-latest
+6 -6
View File
@@ -10,12 +10,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -34,12 +34,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
node-version: "24.13.0"
- uses: pnpm/action-setup@v4
name: Install pnpm
+2 -1
View File
@@ -11,4 +11,5 @@ scripts/_env.sh
.idea
.old
.eslintcache
target
.changelog_backups
target
+20 -13
View File
@@ -9,30 +9,37 @@ if ! command -v pnpm >/dev/null 2>&1; then
exit 1
fi
LOCALE_DIFF="$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src/locales/' || true)"
if [ -n "$LOCALE_DIFF" ]; then
echo "[pre-commit] Locale changes detected. Regenerating i18n types..."
pnpm i18n:types
if [ -d src/types/generated ]; then
echo "[pre-commit] Staging regenerated i18n type artifacts..."
git add src/types/generated
fi
fi
echo "[pre-commit] Running pnpm format before lint..."
pnpm format
echo "[pre-commit] Running lint-staged for JS/TS files..."
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 -9
View File
@@ -25,19 +25,12 @@ 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
echo "[pre-push] All checks passed."
exit 0
+2 -1
View File
@@ -1,7 +1,8 @@
# README.md
# UPDATELOG.md
# Changelog.md
# CONTRIBUTING.md
.changelog_backups
pnpm-lock.yaml
src-tauri/target/
+64 -79
View File
@@ -1,151 +1,136 @@
# CONTRIBUTING
Thank you for your interest in contributing to Clash Verge Rev! This document provides guidelines and instructions to help you set up your development environment and start contributing.
Thank you for your interest in contributing to **Clash Verge Rev**! This guide provides instructions to help you set up your development environment and start contributing effectively.
## Internationalization (i18n)
We welcome translations and improvements to existing locales. Please follow the detailed guidelines in [CONTRIBUTING_i18n.md](docs/CONTRIBUTING_i18n.md) for instructions on extracting strings, file naming conventions, testing translations, and submitting translation PRs.
We welcome translations and improvements to existing locales. For details on contributing translations, please see [CONTRIBUTING_i18n.md](docs/CONTRIBUTING_i18n.md).
## Development Setup
Before you start contributing to the project, you need to set up your development environment. Here are the steps you need to follow:
Before contributing, you need to set up your development environment. Follow the steps below carefully.
### Prerequisites
1. **Install Rust and Node.js**: Our project requires both Rust and Node.js. Please follow the instructions provided [here](https://tauri.app/start/prerequisites/) to install them on your system.
1. **Install Rust and Node.js**
Our project requires both Rust and Node.js. Follow the official installation instructions [here](https://tauri.app/start/prerequisites/).
### Setup for Windows Users
### Windows Users
> [!NOTE]
> **If you are using a Windows ARM device, you additionally need to install [LLVM](https://github.com/llvm/llvm-project/releases) (including clang) and set the environment variable.**
>
> Because the `ring` crate is compiled based on `clang` under Windows ARM.
> [!NOTE]
> **Windows ARM users must also install [LLVM](https://github.com/llvm/llvm-project/releases) (including clang) and set the corresponding environment variables.**
> The `ring` crate depends on `clang` when building on Windows ARM.
If you're a Windows user, you may need to perform some additional steps:
Additional steps for Windows:
- Make sure to add Rust and Node.js to your system's PATH. This is usually done during the installation process, but you can verify and manually add them if necessary.
- The gnu `patch` tool should be installed
- Ensure Rust and Node.js are added to your system `PATH`.
When you setup `Rust` environment, Only use toolchain with `Windows MSVC` , to change settings follow command:
- Install the GNU `patch` tool.
```shell
- Use the MSVC toolchain for Rust:
```bash
rustup target add x86_64-pc-windows-msvc
rustup set default-host x86_64-pc-windows-msvc
```
### Install Node.js Package
### Install Node.js Package Manager
After installing Rust and Node.js, install the necessary Node.js and Node Package Manager:
Enable `corepack`:
```shell
npm install pnpm -g
```bash
corepack enable
```
### Install Dependencies
### Install Project Dependencies
Install node packages
Node.js dependencies:
```shell
```bash
pnpm install
```
Install apt packages ONLY for Ubuntu
Ubuntu-only system packages:
```shell
apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
```bash
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
```
### Download the Mihomo Core Binary
### Download the Mihomo Core Binary (Automatic)
You have two options for downloading the clash binary:
- Automatically download it via the provided script:
```shell
pnpm run prebuild
# Use '--force' or '-f' to update both the Mihomo core version
# and the Clash Verge Rev service version to the latest available.
pnpm run prebuild --force
```
- Manually download it from the [Mihomo release](https://github.com/MetaCubeX/mihomo/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
```bash
pnpm run prebuild
pnpm run prebuild --force # Re-download and overwrite Mihomo core and service binaries
```
### Run the Development Server
To run the development server, use the following command:
```shell
pnpm dev
# If an app instance already exists, use a different command
pnpm dev:diff
# To using tauri built-in dev tool
pnpm dev:tauri
```bash
pnpm dev # Standard
pnpm dev:diff # If an app instance already exists
pnpm dev:tauri # Run Tauri development mode
```
### Build the Project
To build this project:
Standard build:
```shell
```bash
pnpm build
```
For a faster build, use the following command
Fast build for testing:
```shell
```bash
pnpm build:fast
```
This uses Rust's fast-release profile which significantly reduces compilation time by disabling optimization and LTO. The resulting binary will be larger and less performant than the standard build, but it's useful for testing changes quickly.
### Clean Build
The `Artifacts` will display in the `log` in the Terminal.
### Build clean
To clean rust build:
```shell
```bash
pnpm clean
```
### Portable Version (Windows Only)
To package portable version after the build:
```shell
```bash
pnpm portable
```
## Contributing Your Changes
#### Before commit your changes
### Before Committing
If you changed the rust code, it's recommanded to execute code style formatting and quailty checks.
1. Code quailty checks
**Code quality checks:**
```bash
# For rust backend
$ clash-verge-rev: pnpm clippy
# For frontend (not yet).
# Rust backend
cargo clippy-all
# Frontend
pnpm lint
```
2. Code style formatting
**Code formatting:**
```bash
# For rust backend
$ clash-verge-rev: cd src-tauri
$ clash-verge-rev/src-tauri: cargo fmt
# For frontend
$ clash-verge-rev: pnpm format:check
$ clash-verge-rev: pnpm format
# Rust backend
cargo fmt
# Frontend
pnpm format
```
Once you have made your changes:
### 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 and concise commit messages.
4. Push your branch to your fork and submit a pull request to our repository.
We appreciate your contributions and look forward to your active participation in our project!
2. Create a new branch for your feature or bug fix.
3. Commit your changes with clear messages and make sure it's signed.
4. Push your branch and submit a pull request.
We appreciate your contributions and look forward to your participation!
+1271 -1020
View File
File diff suppressed because it is too large Load Diff
+138
View File
@@ -0,0 +1,138 @@
[workspace]
members = [
"src-tauri",
"crates/clash-verge-draft",
"crates/clash-verge-logging",
"crates/clash-verge-signal",
"crates/tauri-plugin-clash-verge-sysinfo",
"crates/clash-verge-types",
"crates/clash-verge-i18n",
]
resolver = "2"
[profile.release]
panic = "abort"
codegen-units = 1
lto = "thin"
opt-level = 3
debug = false
strip = true
overflow-checks = false
rpath = false
[profile.dev]
incremental = true
codegen-units = 64
opt-level = 0
debug = true
strip = "none"
overflow-checks = true
lto = false
rpath = false
[profile.fast-release]
inherits = "release"
codegen-units = 64
incremental = true
lto = false
opt-level = 0
debug = true
strip = false
[workspace.dependencies]
clash-verge-draft = { path = "crates/clash-verge-draft" }
clash-verge-logging = { path = "crates/clash-verge-logging" }
clash-verge-signal = { path = "crates/clash-verge-signal" }
clash-verge-types = { path = "crates/clash-verge-types" }
clash-verge-i18n = { path = "crates/clash-verge-i18n" }
tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" }
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"
criterion = { version = "0.8.1", features = ["async_tokio"] }
tokio = { version = "1.49.0", features = [
"rt-multi-thread",
"macros",
"time",
"sync",
] }
flexi_logger = "0.31.7"
log = "0.4.29"
smartstring = { version = "1.0.1" }
compact_str = { version = "0.9.0", features = ["serde"] }
serde = { version = "1.0.228" }
serde_json = { version = "1.0.149" }
serde_yaml_ng = { version = "0.10.0" }
bitflags = { version = "2.10.0" }
# *** For Windows platform only ***
deelevate = "0.2.0"
# *********************************
[patch.crates-io]
# Patches until https://github.com/tauri-apps/tao/pull/1167 is merged.
tao = { git = "https://github.com/tauri-apps/tao" }
[workspace.lints.clippy]
correctness = { level = "deny", priority = -1 }
suspicious = { level = "deny", priority = -1 }
unwrap_used = "warn"
expect_used = "warn"
panic = "deny"
unimplemented = "deny"
todo = "warn"
dbg_macro = "warn"
clone_on_ref_ptr = "warn"
rc_clone_in_vec_init = "warn"
large_stack_arrays = "warn"
large_const_arrays = "warn"
async_yields_async = "deny"
mutex_atomic = "deny"
mutex_integer = "deny"
rc_mutex = "deny"
unused_async = "deny"
await_holding_lock = "deny"
large_futures = "deny"
future_not_send = "deny"
redundant_else = "deny"
needless_continue = "deny"
needless_raw_string_hashes = "deny"
or_fun_call = "deny"
cognitive_complexity = "deny"
useless_let_if_seq = "deny"
use_self = "deny"
tuple_array_conversions = "deny"
trait_duplication_in_bounds = "deny"
suspicious_operation_groupings = "deny"
string_lit_as_bytes = "deny"
significant_drop_tightening = "deny"
significant_drop_in_scrutinee = "deny"
redundant_clone = "deny"
# option_if_let_else = "deny" // 过于激进,暂时不开启
needless_pass_by_ref_mut = "deny"
needless_collect = "deny"
missing_const_for_fn = "deny"
iter_with_drain = "deny"
iter_on_single_items = "deny"
iter_on_empty_collections = "deny"
# fallible_impl_from = "deny" // 过于激进,暂时不开启
equatable_if_let = "deny"
collection_is_never_read = "deny"
branches_sharing_code = "deny"
pathbuf_init_then_push = "deny"
option_as_ref_cloned = "deny"
large_types_passed_by_value = "deny"
# implicit_clone = "deny" // 可能会造成额外开销,暂时不开启
expl_impl_clone_on_copy = "deny"
copy_iterator = "deny"
cloned_instead_of_copied = "deny"
# self_only_used_in_recursion = "deny" // Since 1.92.0
unnecessary_self_imports = "deny"
unused_trait_names = "deny"
wildcard_imports = "deny"
unnecessary_wraps = "deny"
+49
View File
@@ -0,0 +1,49 @@
## v2.4.5
- **Mihomo(Meta) 内核升级至 v1.19.18**
### 🐞 修复问题
- 修复 macOS 有线网络 DNS 劫持失败
- 修复 Monaco 编辑器内右键菜单显示异常
- 修复设置代理端口时检查端口占用
- 修复 Monaco 编辑器初始化卡 Loading
- 修复恢复备份时 `config.yaml` / `profiles.yaml` 文件内字段未正确恢复
- 修复 Windows 下系统主题同步问题
- 修复 URL Schemes 无法正常导入
- 修复 Linux 下无法安装 TUN 服务
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
- 允许代理页面允许高级过滤搜索
- 备份设置页面新增导入备份按钮
- 允许修改通知弹窗位置
- 支持收起导航栏(导航栏右键菜单 / 界面设置)
- 允许将出站模式显示在托盘一级菜单
- 允许禁用在托盘中显示代理组
- 支持在「编辑节点」中直接导入 AnyTLS URI 配置
- 支持关闭「验证代理绕过格式」
- 新增系统代理绕过和 TUN 排除自定义网段的可视化编辑器
</details>
<details>
<summary><strong> 🚀 优化改进 </strong></summary>
- 应用内更新日志支持解析并渲染 HTML 标签
- 性能优化前后端在渲染流量图时的资源
- 在 Linux NVIDIA 显卡环境下尝试禁用 WebKit DMABUF 渲染以规避潜在问题
- Windows 下自启动改为计划任务实现
- 改进托盘和窗口操作频率限制实现
- 使用「编辑节点」添加节点时,自动将节点添加到第一个 `select` 类型的代理组的第一位
- 隐藏侧边导航栏和悬浮跳转导航的滚动条
- 完善对 AnyTLS / Mieru / Sudoku 的 GUI 支持
- macOS 和 Linux 对服务 IPC 权限进一步限制
- 移除 Windows 自启动计划任务中冗余的 3 秒延时
- 右键错误通知可复制错误详情
- 保存 TUN 设置时优化执行流程,避免界面卡顿
- 补充 `deb` / `rpm` 依赖 `libayatana-appindicator`
- 「连接」表格标题的排序点击区域扩展到整列宽度
</details>
+19 -11
View File
@@ -15,7 +15,9 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
<a href="./docs/README_en.md">English</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_ja.md">日本語</a> ·
<a href="./docs/README_ko.md">한국어</a> ·
<a href="./docs/README_fa.md">فارسی</a>
</p>
## Preview
@@ -46,17 +48,23 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
## Promotion
#### [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
### ✈️ [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
- 海外团队,无跑路风险,高达 50% 返佣
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
- 解锁流媒体及 ChatGPT
- 官网:[https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
🚀 高性能海外技术流机场,支持免费试用与优惠套餐,全面解锁流媒体及 AI 服务,全球首家采用 **QUIC 协议**。
🎁 使用 **Clash Verge 专属邀请链接** 注册即送 **3 天免费试用**,每日 **1GB 流量**:👉 [点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
#### **核心优势:**
- 📱 自研 iOS 客户端(业内"唯一")技术经得起考验,极大**持续研发**投入
- 🧑‍💻 **12小时真人客服**(顺带解决 Clash Verge 使用问题)
- 💰 优惠套餐每月**仅需 21 元,160G 流量,年付 8 折**
- 🌍 海外团队,无跑路风险,高达 50% 返佣
- ⚙️ **集群负载均衡**设计,**负载监控和随时扩容**,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
- ⚡ 全球首家**Quic 协议机场**,现已上线更快的 Tuic 协议(Clash Verge 客户端最佳搭配)
- 🎬 解锁**流媒体及 主流 AI**
🌐 官网:👉 [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
#### 本项目的构建与发布环境由 [YXVM](https://yxvm.com/aff.php?aff=827) 独立服务器全力支持,
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "clash-verge-draft"
version = "0.1.0"
edition = "2024"
[[bench]]
name = "draft_bench"
path = "bench/benche_me.rs"
harness = false
[dependencies]
parking_lot = { workspace = true }
anyhow = { workspace = true }
[dev-dependencies]
criterion = { workspace = true }
tokio = { workspace = true }
@@ -3,17 +3,20 @@ use std::hint::black_box;
use std::process;
use tokio::runtime::Runtime;
use app_lib::config::IVerge;
use app_lib::utils::Draft as DraftNew;
use clash_verge_draft::Draft;
/// 创建测试数据
fn make_draft() -> DraftNew<Box<IVerge>> {
let verge = Box::new(IVerge {
#[derive(Default, Clone, Debug)]
struct IVerge {
enable_auto_launch: Option<bool>,
enable_tun_mode: Option<bool>,
}
fn make_draft() -> Draft<IVerge> {
let verge = IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
..Default::default()
});
DraftNew::from(verge)
};
Draft::new(verge)
}
pub fn bench_draft(c: &mut Criterion) {
@@ -30,18 +33,17 @@ pub fn bench_draft(c: &mut Criterion) {
group.bench_function("data_mut", |b| {
b.iter(|| {
let draft = black_box(make_draft());
let mut data = draft.data_mut();
data.enable_tun_mode = Some(true);
black_box(&data.enable_tun_mode);
draft.edit_draft(|d| d.enable_tun_mode = Some(true));
black_box(&draft.latest_arc().enable_tun_mode);
});
});
group.bench_function("draft_mut_first", |b| {
b.iter(|| {
let draft = black_box(make_draft());
let mut d = draft.draft_mut();
d.enable_auto_launch = Some(false);
black_box(&d.enable_auto_launch);
draft.edit_draft(|d| d.enable_auto_launch = Some(false));
let latest = draft.latest_arc();
black_box(&latest.enable_auto_launch);
});
});
@@ -49,20 +51,24 @@ pub fn bench_draft(c: &mut Criterion) {
b.iter(|| {
let draft = black_box(make_draft());
{
let mut first = draft.draft_mut();
first.enable_tun_mode = Some(true);
black_box(&first.enable_tun_mode);
draft.edit_draft(|d| {
d.enable_tun_mode = Some(true);
});
let latest1 = draft.latest_arc();
black_box(&latest1.enable_tun_mode);
}
let mut second = draft.draft_mut();
second.enable_tun_mode = Some(false);
black_box(&second.enable_tun_mode);
draft.edit_draft(|d| {
d.enable_tun_mode = Some(false);
});
let latest2 = draft.latest_arc();
black_box(&latest2.enable_tun_mode);
});
});
group.bench_function("latest_ref", |b| {
group.bench_function("latest_arc", |b| {
b.iter(|| {
let draft = black_box(make_draft());
let latest = draft.latest_ref();
let latest = draft.latest_arc();
black_box(&latest.enable_auto_launch);
});
});
@@ -71,8 +77,9 @@ pub fn bench_draft(c: &mut Criterion) {
b.iter(|| {
let draft = black_box(make_draft());
{
let mut d = draft.draft_mut();
d.enable_auto_launch = Some(false);
draft.edit_draft(|d| {
d.enable_auto_launch = Some(false);
});
}
draft.apply();
black_box(&draft);
@@ -83,8 +90,9 @@ pub fn bench_draft(c: &mut Criterion) {
b.iter(|| {
let draft = black_box(make_draft());
{
let mut d = draft.draft_mut();
d.enable_auto_launch = Some(false);
draft.edit_draft(|d| {
d.enable_auto_launch = Some(false);
});
}
draft.discard();
black_box(&draft);
@@ -95,9 +103,8 @@ pub fn bench_draft(c: &mut Criterion) {
b.to_async(&rt).iter(|| async {
let draft = black_box(make_draft());
let _: Result<(), anyhow::Error> = draft
.with_data_modify::<_, _, _, anyhow::Error>(|mut box_data| async move {
box_data.enable_auto_launch =
Some(!box_data.enable_auto_launch.unwrap_or(false));
.with_data_modify::<_, _, _>(|mut box_data| async move {
box_data.enable_auto_launch = Some(!box_data.enable_auto_launch.unwrap_or(false));
Ok((box_data, ()))
})
.await;
+92
View File
@@ -0,0 +1,92 @@
use parking_lot::RwLock;
use std::sync::Arc;
pub type SharedDraft<T> = Arc<T>;
type DraftInner<T> = (SharedDraft<T>, Option<SharedDraft<T>>);
/// Draft 管理:committed 与 optional draft 都以 Arc<Box<T>> 存储,
// (committed_snapshot, optional_draft_snapshot)
#[derive(Debug, Clone)]
pub struct Draft<T: Clone> {
inner: Arc<RwLock<DraftInner<T>>>,
}
impl<T: Clone> Draft<T> {
#[inline]
pub fn new(data: T) -> Self {
Self {
inner: Arc::new(RwLock::new((Arc::new(data), None))),
}
}
/// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc)
#[inline]
pub fn data_arc(&self) -> SharedDraft<T> {
let guard = self.inner.read();
Arc::clone(&guard.0)
}
/// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
/// 这也是零拷贝:只 clone Arc,不 clone T
#[inline]
pub fn latest_arc(&self) -> SharedDraft<T> {
let guard = self.inner.read();
guard.1.clone().unwrap_or_else(|| Arc::clone(&guard.0))
}
/// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T)
/// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T;
/// - 若草稿被其他读者共享,Arc::make_mut 会做一次 T.clone(最小必要拷贝)。
#[inline]
pub fn edit_draft<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
let mut guard = self.inner.write();
let mut draft_arc = guard.1.take().unwrap_or_else(|| Arc::clone(&guard.0));
let data_mut = Arc::make_mut(&mut draft_arc);
let result = f(data_mut);
guard.1 = Some(draft_arc);
result
}
/// 将草稿提交到已提交位置(替换),并清除草稿
#[inline]
pub fn apply(&self) {
let mut guard = self.inner.write();
if let Some(d) = guard.1.take() {
guard.0 = d;
}
}
/// 丢弃草稿(如果存在)
#[inline]
pub fn discard(&self) {
let mut guard = self.inner.write();
guard.1 = None;
}
/// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
/// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
#[inline]
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
where
T: Send + Sync + 'static,
F: FnOnce(T) -> Fut + Send,
Fut: std::future::Future<Output = Result<(T, R), anyhow::Error>> + Send,
{
let (local, original_arc) = {
let guard = self.inner.read();
let arc = Arc::clone(&guard.0);
((*arc).clone(), arc)
};
let (new_local, res) = f(local).await?;
let mut guard = self.inner.write();
if !Arc::ptr_eq(&guard.0, &original_arc) {
return Err(anyhow::anyhow!(
"Optimistic lock failed: Committed data has changed during async operation"
));
}
guard.0 = Arc::from(new_local);
Ok(res)
}
}
+263
View File
@@ -0,0 +1,263 @@
#[cfg(test)]
mod tests {
use anyhow::anyhow;
use clash_verge_draft::Draft;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
#[derive(Clone, Debug, Default, PartialEq)]
struct IVerge {
enable_auto_launch: Option<bool>,
enable_tun_mode: Option<bool>,
}
// Minimal single-threaded executor for immediately-ready futures
fn block_on_ready<F: Future>(fut: F) -> F::Output {
fn no_op_raw_waker() -> RawWaker {
fn clone(_: *const ()) -> RawWaker {
no_op_raw_waker()
}
fn wake(_: *const ()) {}
fn wake_by_ref(_: *const ()) {}
fn drop(_: *const ()) {}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
RawWaker::new(std::ptr::null(), &VTABLE)
}
let waker = unsafe { Waker::from_raw(no_op_raw_waker()) };
let mut cx = Context::from_waker(&waker);
let mut fut = Box::pin(fut);
loop {
match Pin::as_mut(&mut fut).poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => std::thread::yield_now(),
}
}
}
#[test]
fn test_draft_basic_flow() {
let verge = IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
};
let draft = Draft::new(verge);
// 读取正式数据(data_arc)
{
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false));
}
// 修改草稿(使用 edit_draft)
draft.edit_draft(|d| {
d.enable_auto_launch = Some(false);
d.enable_tun_mode = Some(true);
});
// 正式数据未变
{
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false));
}
// 草稿已变
{
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(false));
assert_eq!(latest.enable_tun_mode, Some(true));
}
// 提交草稿
draft.apply();
// 正式数据已更新
{
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true));
}
// 新一轮草稿并修改
draft.edit_draft(|d| {
d.enable_auto_launch = Some(true);
});
{
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(true));
assert_eq!(latest.enable_tun_mode, Some(true));
}
// 丢弃草稿
draft.discard();
// 丢弃后再次创建草稿,会从已提交重新 clone
{
draft.edit_draft(|d| {
// 原 committed 是 enable_auto_launch = Some(false)
assert_eq!(d.enable_auto_launch, Some(false));
// 再修改一下
d.enable_tun_mode = Some(false);
});
// 草稿中值已修改,但正式数据仍是 apply 后的值
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true));
}
}
#[test]
fn test_arc_pointer_behavior_on_edit_and_apply() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
});
// 初始 latest == committed
let committed = draft.data_arc();
let latest = draft.latest_arc();
assert!(std::sync::Arc::ptr_eq(&committed, &latest));
// 第一次 edit:由于与 committed 共享,Arc::make_mut 会克隆
draft.edit_draft(|d| d.enable_tun_mode = Some(true));
let committed_after_first_edit = draft.data_arc();
let draft_after_first_edit = draft.latest_arc();
assert!(!std::sync::Arc::ptr_eq(
&committed_after_first_edit,
&draft_after_first_edit
));
// 提交会把 committed 指向草稿的 Arc
let prev_draft_ptr = std::sync::Arc::as_ptr(&draft_after_first_edit);
draft.apply();
let committed_after_apply = draft.data_arc();
assert_eq!(std::sync::Arc::as_ptr(&committed_after_apply), prev_draft_ptr);
// 第二次编辑:此时草稿唯一持有(无其它引用),不应再克隆
// 获取草稿 Arc 的指针并立即丢弃本地引用,避免增加 strong_count
draft.edit_draft(|d| d.enable_auto_launch = Some(false));
let latest1 = draft.latest_arc();
let latest1_ptr = std::sync::Arc::as_ptr(&latest1);
drop(latest1); // 确保只有 Draft 内部持有草稿 Arc
// 再次编辑(unique,Arc::make_mut 不应克隆)
draft.edit_draft(|d| d.enable_tun_mode = Some(false));
let latest2 = draft.latest_arc();
let latest2_ptr = std::sync::Arc::as_ptr(&latest2);
assert_eq!(latest1_ptr, latest2_ptr, "Unique edit should not clone Arc");
assert_eq!(latest2.enable_auto_launch, Some(false));
assert_eq!(latest2.enable_tun_mode, Some(false));
}
#[test]
fn test_discard_restores_latest_to_committed() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 创建草稿并修改
draft.edit_draft(|d| d.enable_auto_launch = Some(true));
let committed = draft.data_arc();
let latest = draft.latest_arc();
assert!(!std::sync::Arc::ptr_eq(&committed, &latest));
// 丢弃草稿后 latest 应回到 committed
draft.discard();
let committed2 = draft.data_arc();
let latest2 = draft.latest_arc();
assert!(std::sync::Arc::ptr_eq(&committed2, &latest2));
assert_eq!(latest2.enable_auto_launch, Some(false));
}
#[test]
fn test_edit_draft_returns_closure_result() {
let draft = Draft::new(IVerge::default());
let ret = draft.edit_draft(|d| {
d.enable_tun_mode = Some(true);
123usize
});
assert_eq!(ret, 123);
let latest = draft.latest_arc();
assert_eq!(latest.enable_tun_mode, Some(true));
}
#[test]
fn test_with_data_modify_ok_and_replaces_committed() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 使用 with_data_modify 异步(立即就绪)地更新 committed
let res = block_on_ready(draft.with_data_modify(|mut v| async move {
v.enable_auto_launch = Some(true);
Ok((v, "done"))
}));
assert_eq!(
{
#[allow(clippy::unwrap_used)]
res.unwrap()
},
"done"
);
let committed = draft.data_arc();
assert_eq!(committed.enable_auto_launch, Some(true));
assert_eq!(committed.enable_tun_mode, Some(false));
}
#[test]
fn test_with_data_modify_error_propagation() {
let draft = Draft::new(IVerge::default());
#[allow(clippy::unwrap_used)]
let err = block_on_ready(draft.with_data_modify(|_v| async move { Err::<(IVerge, ()), _>(anyhow!("boom")) }))
.unwrap_err();
assert_eq!(format!("{err}"), "boom");
}
#[test]
fn test_with_data_modify_does_not_touch_existing_draft() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 创建草稿并修改
draft.edit_draft(|d| {
d.enable_auto_launch = Some(true);
d.enable_tun_mode = Some(true);
});
let draft_before = draft.latest_arc();
let draft_before_ptr = std::sync::Arc::as_ptr(&draft_before);
// 同时通过 with_data_modify 修改 committed
#[allow(clippy::unwrap_used)]
block_on_ready(draft.with_data_modify(|mut v| async move {
v.enable_auto_launch = Some(false); // 与草稿不同
Ok((v, ()))
}))
.unwrap();
// 草稿应保持不变
let draft_after = draft.latest_arc();
assert_eq!(
std::sync::Arc::as_ptr(&draft_after),
draft_before_ptr,
"Existing draft should not be replaced by with_data_modify"
);
assert_eq!(draft_after.enable_auto_launch, Some(true));
assert_eq!(draft_after.enable_tun_mode, Some(true));
// 丢弃草稿后 latest == committed,且 committed 为异步修改结果
draft.discard();
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(false));
assert_eq!(latest.enable_tun_mode, Some(false));
}
}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "clash-verge-i18n"
version = "0.1.0"
edition = "2024"
[dependencies]
rust-i18n = "3.1.5"
sys-locale = "0.3.2"
[lints]
workspace = true
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Rule
direct: Direct
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Regel
direct: Direkt
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Rule
direct: Direct
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Regla
direct: Directo
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Rule
direct: Direct
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Aturan
direct: Langsung
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: ルール
direct: ダイレクト
global: グローバル
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: 대시보드
body: 대시보드 표시 상태가 업데이트되었습니다.
clashModeChanged:
title: 모드 전환
body: "{mode}(으)로 전환되었습니다."
systemProxyToggled:
title: 시스템 프록시
body: 시스템 프록시 상태가 업데이트되었습니다.
tunModeToggled:
title: TUN 모드
body: TUN 모드 상태가 업데이트되었습니다.
lightweightModeEntered:
title: 경량 모드
body: 경량 모드에 진입했습니다.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: 곧 종료
body: Clash Verge가 곧 종료됩니다.
appHidden:
title: 앱이 숨겨짐
body: Clash Verge가 백그라운드에서 실행 중입니다.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: 대시보드
ruleMode: 규칙 모드
globalMode: 전역 모드
directMode: 직접 모드
outboundModes: Outbound Modes
rule: 규칙
direct: 직접
global: 글로벌
profiles: 프로필
proxies: 프록시
systemProxy: 시스템 프록시
tunMode: TUN 모드
closeAllConnections: 모든 연결 닫기
lightweightMode: 경량 모드
copyEnv: 환경 변수 복사
confDir: 구성 디렉터리
coreDir: 코어 디렉터리
logsDir: 로그 디렉터리
openDir: 디렉터리 열기
appLog: 애플리케이션 로그
coreLog: 코어 로그
restartClash: Clash 코어 재시작
restartApp: 애플리케이션 재시작
vergeVersion: Verge 버전
more: 더 보기
exit: 종료
tooltip:
systemProxy: 시스템 프록시
tun: TUN
profile: 프로필
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Правило
direct: Прямой
global: Глобальный
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Kural
direct: Doğrudan
global: Küresel
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: Dashboard
body: Dashboard visibility has been updated.
clashModeChanged:
title: Mode Switch
body: Switched to {mode}.
systemProxyToggled:
title: System Proxy
body: System proxy status has been updated.
tunModeToggled:
title: TUN Mode
body: TUN mode status has been updated.
lightweightModeEntered:
title: Lightweight Mode
body: Entered lightweight mode.
profilesReactivated:
title: Profiles
body: Profile Reactivated.
appQuit:
title: About to Exit
body: Clash Verge is about to exit.
appHidden:
title: Application Hidden
body: Clash Verge is running in the background.
service:
adminInstallPrompt: Installing the Clash Verge service requires administrator privileges.
adminUninstallPrompt: Uninstalling the Clash Verge service requires administrator privileges.
tray:
dashboard: Dashboard
ruleMode: Rule Mode
globalMode: Global Mode
directMode: Direct Mode
outboundModes: Outbound Modes
rule: Rule
direct: Direct
global: Global
profiles: Profiles
proxies: Proxies
systemProxy: System Proxy
tunMode: TUN Mode
closeAllConnections: Close All Connections
lightweightMode: Lightweight Mode
copyEnv: Copy Environment Variables
confDir: Configuration Directory
coreDir: Core Directory
logsDir: Log Directory
openDir: Open Directory
appLog: Application Log
coreLog: Core Log
restartClash: Restart Clash Core
restartApp: Restart Application
vergeVersion: Verge Version
more: More
exit: Exit
tooltip:
systemProxy: System Proxy
tun: TUN
profile: Profile
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: 仪表板
body: 仪表板显示状态已更新。
clashModeChanged:
title: 模式切换
body: 已切换至 {mode}。
systemProxyToggled:
title: 系统代理
body: 系统代理状态已更新。
tunModeToggled:
title: TUN 模式
body: TUN 模式状态已更新。
lightweightModeEntered:
title: 轻量模式
body: 已进入轻量模式。
profilesReactivated:
title: 订阅
body: 订阅已激活。
appQuit:
title: 即将退出
body: Clash Verge 即将退出。
appHidden:
title: 应用已隐藏
body: Clash Verge 正在后台运行。
service:
adminInstallPrompt: 安装 Clash Verge 服务需要管理员权限
adminUninstallPrompt: 卸载 Clash Verge 服务需要管理员权限
tray:
dashboard: 仪表板
ruleMode: 规则模式
globalMode: 全局模式
directMode: 直连模式
outboundModes: 出站模式
rule: 规则
direct: 直连
global: 全局
profiles: 订阅
proxies: 代理
systemProxy: 系统代理
tunMode: TUN 模式
closeAllConnections: 关闭所有连接
lightweightMode: 轻量模式
copyEnv: 复制环境变量
confDir: 配置目录
coreDir: 内核目录
logsDir: 日志目录
openDir: 打开目录
appLog: 应用日志
coreLog: 内核日志
restartClash: 重启 Clash 内核
restartApp: 重启应用
vergeVersion: Verge 版本
more: 更多
exit: 退出
tooltip:
systemProxy: 系统代理
tun: TUN
profile: 订阅
+60
View File
@@ -0,0 +1,60 @@
_version: 1
notifications:
dashboardToggled:
title: 儀表板
body: 儀表板顯示狀態已更新。
clashModeChanged:
title: 模式切換
body: 已切換至 {mode}。
systemProxyToggled:
title: 系統代理
body: 系統代理狀態已更新。
tunModeToggled:
title: 虛擬網路介面卡模式
body: 已更新虛擬網路介面卡模式狀態。
lightweightModeEntered:
title: 輕量模式
body: 已進入輕量模式。
profilesReactivated:
title: 訂閱
body: 訂閱已啟用。
appQuit:
title: 即將退出
body: Clash Verge 即將退出。
appHidden:
title: 應用已隱藏
body: Clash Verge 正在背景執行。
service:
adminInstallPrompt: 安裝 Clash Verge 服務需要管理員權限
adminUninstallPrompt: 卸载 Clash Verge 服務需要管理員權限
tray:
dashboard: 儀表板
ruleMode: 規則模式
globalMode: 全域模式
directMode: 直連模式
outboundModes: 出站模式
rule: 規則
direct: 直連
global: 全域
profiles: 訂閱
proxies: 代理
systemProxy: 系統代理
tunMode: 虛擬網路介面卡模式
closeAllConnections: 關閉所有連線
lightweightMode: 輕量模式
copyEnv: 複製環境變數
confDir: 設定目錄
coreDir: 核心目錄
logsDir: 日誌目錄
openDir: 開啟目錄
appLog: 應用程式日誌
coreLog: 核心日誌
restartClash: 重新啟動 Clash 核心
restartApp: 重新啟動應用程式
vergeVersion: Verge 版本
more: 更多
exit: 離開
tooltip:
systemProxy: 系統代理
tun: 虛擬網路介面卡
profile: 訂閱
+103
View File
@@ -0,0 +1,103 @@
use rust_i18n::i18n;
const DEFAULT_LANGUAGE: &str = "zh";
i18n!("locales", fallback = "zh");
#[inline]
fn locale_alias(locale: &str) -> Option<&'static str> {
match locale {
"ja" | "ja-jp" | "jp" => Some("jp"),
"zh" | "zh-cn" | "zh-hans" | "zh-sg" | "zh-my" | "zh-chs" => Some("zh"),
"zh-tw" | "zh-hk" | "zh-hant" | "zh-mo" | "zh-cht" => Some("zhtw"),
_ => None,
}
}
#[inline]
fn resolve_supported_language(language: &str) -> Option<&'static str> {
if language.is_empty() {
return None;
}
let normalized = language.to_lowercase().replace('_', "-");
let segments: Vec<&str> = normalized.split('-').collect();
let supported = rust_i18n::available_locales!();
for i in (1..=segments.len()).rev() {
let prefix = segments[..i].join("-");
if let Some(alias) = locale_alias(&prefix)
&& let Some(&found) = supported.iter().find(|&&l| l.eq_ignore_ascii_case(alias))
{
return Some(found);
}
if let Some(&found) = supported.iter().find(|&&l| l.eq_ignore_ascii_case(&prefix)) {
return Some(found);
}
}
None
}
#[inline]
fn current_language(language: Option<&str>) -> &str {
language
.as_ref()
.filter(|lang| !lang.is_empty())
.and_then(|lang| resolve_supported_language(lang))
.unwrap_or_else(system_language)
}
#[inline]
pub fn system_language() -> &'static str {
sys_locale::get_locale()
.as_deref()
.and_then(resolve_supported_language)
.unwrap_or(DEFAULT_LANGUAGE)
}
#[inline]
pub fn sync_locale(language: Option<&str>) {
let language = current_language(language);
set_locale(language);
}
#[inline]
pub fn set_locale(language: &str) {
let lang = resolve_supported_language(language).unwrap_or(DEFAULT_LANGUAGE);
rust_i18n::set_locale(lang);
}
#[inline]
pub fn translate(key: &str) -> Cow<'_, str> {
rust_i18n::t!(key)
}
#[macro_export]
macro_rules! t {
($key:expr) => {
$crate::translate(&$key)
};
($key:expr, $($arg_name:ident = $arg_value:expr),*) => {
{
let mut _text = $crate::translate(&$key);
$(
_text = _text.replace(&format!("{{{}}}", stringify!($arg_name)), &$arg_value);
)*
_text
}
};
}
#[cfg(test)]
mod test {
use super::resolve_supported_language;
#[test]
fn test_resolve_supported_language() {
assert_eq!(resolve_supported_language("en"), Some("en"));
assert_eq!(resolve_supported_language("en-US"), Some("en"));
assert_eq!(resolve_supported_language("zh"), Some("zh"));
assert_eq!(resolve_supported_language("zh-CN"), Some("zh"));
assert_eq!(resolve_supported_language("zh-Hant"), Some("zhtw"));
assert_eq!(resolve_supported_language("jp"), Some("jp"));
assert_eq!(resolve_supported_language("ja-JP"), Some("jp"));
assert_eq!(resolve_supported_language("fr"), None);
}
}
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "clash-verge-logging"
version = "0.1.0"
edition = "2024"
[dependencies]
log = { workspace = true }
tokio = { workspace = true }
compact_str = { workspace = true }
flexi_logger = { workspace = true }
[features]
default = []
+129
View File
@@ -0,0 +1,129 @@
use compact_str::CompactString;
use flexi_logger::DeferredNow;
use flexi_logger::filter::LogLineFilter;
use flexi_logger::writers::FileLogWriter;
use flexi_logger::writers::LogWriter as _;
use log::Level;
use log::Record;
use std::{fmt, sync::Arc};
use tokio::sync::{Mutex, MutexGuard};
pub type SharedWriter = Arc<Mutex<FileLogWriter>>;
#[derive(Debug, PartialEq, Eq)]
pub enum Type {
Cmd,
Core,
Config,
Setup,
System,
SystemSignal,
Service,
Hotkey,
Window,
Tray,
Timer,
Frontend,
Backup,
File,
Lightweight,
Network,
ProxyMode,
Validate,
ClashVergeRev,
}
impl fmt::Display for Type {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cmd => write!(f, "[Cmd]"),
Self::Core => write!(f, "[Core]"),
Self::Config => write!(f, "[Config]"),
Self::Setup => write!(f, "[Setup]"),
Self::System => write!(f, "[System]"),
Self::SystemSignal => write!(f, "[SysSignal]"),
Self::Service => write!(f, "[Service]"),
Self::Hotkey => write!(f, "[Hotkey]"),
Self::Window => write!(f, "[Window]"),
Self::Tray => write!(f, "[Tray]"),
Self::Timer => write!(f, "[Timer]"),
Self::Frontend => write!(f, "[Frontend]"),
Self::Backup => write!(f, "[Backup]"),
Self::File => write!(f, "[File]"),
Self::Lightweight => write!(f, "[Lightweight]"),
Self::Network => write!(f, "[Network]"),
Self::ProxyMode => write!(f, "[ProxMode]"),
Self::Validate => write!(f, "[Validate]"),
Self::ClashVergeRev => write!(f, "[ClashVergeRev]"),
}
}
}
#[macro_export]
macro_rules! logging {
// 不带 print 参数的版本(默认不打印)
($level:ident, $type:expr, $($arg:tt)*) => {
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! logging_error {
// Handle Result<T, E>
($type:expr, $expr:expr) => {
if let Err(err) = $expr {
log::error!(target: "app", "[{}] {}", $type, err);
}
};
// Handle formatted message: always print to stdout and log as error
($type:expr, $fmt:literal $(, $arg:expr)*) => {
log::error!(target: "app", "[{}] {}", $type, format_args!($fmt $(, $arg)*));
};
}
#[inline]
pub fn write_sidecar_log(
writer: MutexGuard<'_, FileLogWriter>,
now: &mut DeferredNow,
level: Level,
message: &CompactString,
) {
let args = format_args!("{}", message);
let record = Record::builder().args(args).level(level).target("sidecar").build();
let _ = writer.write(now, &record);
}
pub struct NoModuleFilter<'a>(pub Vec<&'a str>);
impl<'a> NoModuleFilter<'a> {
#[inline]
pub fn filter(&self, record: &Record) -> bool {
if let Some(module) = record.module_path() {
for blocked in self.0.iter() {
if module.len() >= blocked.len() && module.as_bytes()[..blocked.len()] == blocked.as_bytes()[..] {
return false;
}
}
}
true
}
}
impl<'a> LogLineFilter for NoModuleFilter<'a> {
#[inline]
fn write(
&self,
now: &mut DeferredNow,
record: &Record,
writer: &dyn flexi_logger::filter::LogLineWriter,
) -> std::io::Result<()> {
if !self.filter(record) {
return Ok(());
}
writer.write(now, record)
}
}
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "clash-verge-signal"
version = "0.1.0"
edition = "2024"
rust-version = "1.91"
[dependencies]
clash-verge-logging = { workspace = true }
log = { workspace = true }
tokio = { workspace = true }
[lints]
workspace = true
+35
View File
@@ -0,0 +1,35 @@
use std::sync::OnceLock;
use clash_verge_logging::{Type, logging};
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
pub(crate) static RUNTIME: OnceLock<Option<tokio::runtime::Runtime>> = OnceLock::new();
pub fn register<F, Fut>(f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
{
RUNTIME.get_or_init(|| match tokio::runtime::Runtime::new() {
Ok(rt) => Some(rt),
Err(e) => {
logging!(
info,
Type::SystemSignal,
"register shutdown signal failed, create tokio runtime error: {}",
e
);
None
}
});
#[cfg(unix)]
unix::register(f);
#[cfg(windows)]
windows::register(f);
}
+79
View File
@@ -0,0 +1,79 @@
use std::sync::atomic::{AtomicBool, Ordering};
use clash_verge_logging::{Type, logging};
use tokio::signal::unix::{SignalKind, signal};
use crate::RUNTIME;
static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false);
pub fn register<F, Fut>(f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
{
if let Some(Some(rt)) = RUNTIME.get() {
rt.spawn(async move {
let mut sigterm = match signal(SignalKind::terminate()) {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register SIGTERM: {}", e);
return;
}
};
let mut sigint = match signal(SignalKind::interrupt()) {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register SIGINT: {}", e);
return;
}
};
let mut sighup = match signal(SignalKind::hangup()) {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register SIGHUP: {}", e);
return;
}
};
loop {
let signal_name;
tokio::select! {
_ = sigterm.recv() => {
signal_name = "SIGTERM";
}
_ = sigint.recv() => {
signal_name = "SIGINT";
}
_ = sighup.recv() => {
signal_name = "SIGHUP";
}
else => {
break;
}
}
if IS_CLEANING_UP.load(Ordering::SeqCst) {
logging!(
info,
Type::SystemSignal,
"Already shutting down, ignoring repeated signal: {}",
signal_name
);
continue;
}
IS_CLEANING_UP.store(true, Ordering::SeqCst);
logging!(info, Type::SystemSignal, "Caught signal {}", signal_name);
f().await;
}
});
} else {
logging!(
error,
Type::SystemSignal,
"register shutdown signal failed, RUNTIME is not available"
);
}
}
+89
View File
@@ -0,0 +1,89 @@
use std::sync::atomic::{AtomicBool, Ordering};
use clash_verge_logging::{Type, logging};
use tokio::signal::windows;
use crate::RUNTIME;
static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false);
pub fn register<F, Fut>(f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
{
if let Some(Some(rt)) = RUNTIME.get() {
rt.spawn(async move {
let mut ctrl_c = match windows::ctrl_c() {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register Ctrl+C: {}", e);
return;
}
};
let mut ctrl_close = match windows::ctrl_close() {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Close: {}", e);
return;
}
};
let mut ctrl_shutdown = match windows::ctrl_shutdown() {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Shutdown: {}", e);
return;
}
};
let mut ctrl_logoff = match windows::ctrl_logoff() {
Ok(s) => s,
Err(e) => {
logging!(error, Type::SystemSignal, "Failed to register Ctrl+Logoff: {}", e);
return;
}
};
loop {
let signal_name;
tokio::select! {
_ = ctrl_c.recv() => {
signal_name = "Ctrl+C";
}
_ = ctrl_close.recv() => {
signal_name = "Ctrl+Close";
}
_ = ctrl_shutdown.recv() => {
signal_name = "Ctrl+Shutdown";
}
_ = ctrl_logoff.recv() => {
signal_name = "Ctrl+Logoff";
}
}
if IS_CLEANING_UP.load(Ordering::SeqCst) {
logging!(
info,
Type::SystemSignal,
"Already shutting down, ignoring repeated signal: {}",
signal_name
);
continue;
}
IS_CLEANING_UP.store(true, Ordering::SeqCst);
logging!(info, Type::SystemSignal, "Caught Windows signal: {}", signal_name);
f().await;
}
});
} else {
logging!(
error,
Type::SystemSignal,
"register shutdown signal failed, RUNTIME is not available"
);
}
}
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "clash-verge-types"
version = "0.1.0"
edition = "2024"
rust-version = "1.91"
[dependencies]
serde = { workspace = true }
serde_yaml_ng = { workspace = true }
smartstring = { workspace = true }
[lints]
workspace = true
+1
View File
@@ -0,0 +1 @@
pub mod runtime;
+148
View File
@@ -0,0 +1,148 @@
use serde_yaml_ng::{Mapping, Value};
use smartstring::alias::String;
use std::collections::{HashMap, HashSet};
const PATCH_CONFIG_INNER: [&str; 4] = ["allow-lan", "ipv6", "log-level", "unified-delay"];
#[derive(Default, Clone)]
pub struct IRuntime {
pub config: Option<Mapping>,
// 记录在订阅中(包括merge和script生成的)出现过的keys
// 这些keys不一定都生效
pub exists_keys: HashSet<String>,
// TODO 或许可以用 FixMap 来存储以提升效率
pub chain_logs: HashMap<String, Vec<(String, String)>>,
}
impl IRuntime {
#[inline]
pub fn new() -> Self {
Self::default()
}
// 这里只更改 allow-lan | ipv6 | log-level | tun
#[inline]
pub fn patch_config(&mut self, patch: &Mapping) {
let config = if let Some(config) = self.config.as_mut() {
config
} else {
return;
};
for key in PATCH_CONFIG_INNER.iter() {
if let Some(value) = patch.get(key) {
config.insert((*key).into(), value.clone());
}
}
let patch_tun = patch.get("tun");
if let Some(patch_tun_value) = patch_tun {
let mut tun = config
.get("tun")
.and_then(|val| val.as_mapping())
.cloned()
.unwrap_or_else(Mapping::new);
if let Some(patch_tun_mapping) = patch_tun_value.as_mapping() {
for key in use_keys(patch_tun_mapping) {
if let Some(value) = patch_tun_mapping.get(key.as_str()) {
tun.insert(Value::from(key.as_str()), value.clone());
}
}
}
config.insert("tun".into(), Value::from(tun));
}
}
/// 更新链式代理配置
///
/// 该函数更新 `proxies` 和 `proxy-groups` 配置,并处理链式代理的修改或(传入 None )删除。
///
/// 配置示例:
///
/// ```json
/// {
/// "proxies": [
/// {
/// "name": "入口节点",
/// "type": "xxx",
/// "server": "xxx",
/// "port": "xxx",
/// "ports": "xxx",
/// "password": "xxx",
/// "skip-cert-verify": "xxx"
/// },
/// {
/// "name": "hop_node_1_xxxx",
/// "type": "xxx",
/// "server": "xxx",
/// "port": "xxx",
/// "ports": "xxx",
/// "password": "xxx",
/// "skip-cert-verify": "xxx",
/// "dialer-proxy": "入口节点"
/// },
/// {
/// "name": "出口节点",
/// "type": "xxx",
/// "server": "xxx",
/// "port": "xxx",
/// "ports": "xxx",
/// "password": "xxx",
/// "skip-cert-verify": "xxx",
/// "dialer-proxy": "hop_node_1_xxxx"
/// }
/// ],
/// "proxy-groups": [
/// {
/// "name": "proxy_chain",
/// "type": "select",
/// "proxies": ["出口节点"]
/// }
/// ]
/// }
/// ```
#[inline]
pub fn update_proxy_chain_config(&mut self, proxy_chain_config: Option<Value>) {
let config = if let Some(config) = self.config.as_mut() {
config
} else {
return;
};
if let Some(Value::Sequence(proxies)) = config.get_mut("proxies") {
proxies.iter_mut().for_each(|proxy| {
if let Some(proxy) = proxy.as_mapping_mut()
&& proxy.get("dialer-proxy").is_some()
{
proxy.remove("dialer-proxy");
}
});
}
if let Some(Value::Sequence(dialer_proxies)) = proxy_chain_config
&& let Some(Value::Sequence(proxies)) = config.get_mut("proxies")
{
for (i, dialer_proxy) in dialer_proxies.iter().enumerate() {
if let Some(Value::Mapping(proxy)) =
proxies.iter_mut().find(|proxy| proxy.get("name") == Some(dialer_proxy))
&& i != 0
&& let Some(dialer_proxy) = dialer_proxies.get(i - 1)
{
proxy.insert("dialer-proxy".into(), dialer_proxy.to_owned());
}
}
}
}
}
// TODO 完整迁移 enhance 行为后移除
#[inline]
fn use_keys<'a>(config: &'a Mapping) -> impl Iterator<Item = String> + 'a {
config.iter().filter_map(|(key, _)| key.as_str()).map(|s: &str| {
let mut s: String = s.into();
s.make_ascii_lowercase();
s
})
}
@@ -0,0 +1,20 @@
[package]
name = "tauri-plugin-clash-verge-sysinfo"
version = "0.1.0"
edition = "2024"
rust-version = "1.91"
[dependencies]
tauri = { workspace = true }
tauri-plugin-clipboard-manager = { workspace = true }
parking_lot = { workspace = true }
sysinfo = { version = "0.37.2", features = ["network", "system"] }
[target.'cfg(not(windows))'.dependencies]
libc = "0.2.180"
[target.'cfg(windows)'.dependencies]
deelevate = { workspace = true }
[lints]
workspace = true
@@ -0,0 +1,33 @@
use parking_lot::RwLock;
use tauri::{AppHandle, Runtime, State, command};
use tauri_plugin_clipboard_manager::{ClipboardExt as _, Error};
use crate::Platform;
// TODO 迁移,让新的结构体允许通过 tauri command 正确使用 structure.field 方式获取信息
#[command]
pub fn get_system_info(state: State<'_, RwLock<Platform>>) -> Result<String, Error> {
Ok(state.inner().read().to_string())
}
/// 获取应用的运行时间(毫秒)
#[command]
pub fn get_app_uptime(state: State<'_, RwLock<Platform>>) -> Result<u128, Error> {
Ok(state.inner().read().appinfo.app_startup_time.elapsed().as_millis())
}
/// 检查应用是否以管理员身份运行
#[command]
pub fn app_is_admin(state: State<'_, RwLock<Platform>>) -> Result<bool, Error> {
Ok(state.inner().read().appinfo.app_is_admin)
}
#[command]
pub fn export_diagnostic_info<R: Runtime>(
app_handle: AppHandle<R>,
state: State<'_, RwLock<Platform>>,
) -> Result<(), Error> {
let info = state.inner().read().to_string();
let clipboard = app_handle.clipboard();
clipboard.write_text(info)
}
@@ -0,0 +1,174 @@
use std::{
fmt::{Debug, Display},
time::Instant,
};
pub mod commands;
#[cfg(windows)]
use deelevate::{PrivilegeLevel, Token};
#[cfg(unix)]
pub use libc;
use parking_lot::RwLock;
use sysinfo::{Networks, System};
use tauri::{
Manager as _, Runtime,
plugin::{Builder, TauriPlugin},
};
pub struct SysInfo {
system_name: String,
system_version: String,
system_kernel_version: String,
system_arch: String,
}
impl Default for SysInfo {
#[inline]
fn default() -> Self {
let system_name = System::name().unwrap_or_else(|| "Null".into());
let system_version = System::long_os_version().unwrap_or_else(|| "Null".into());
let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into());
let system_arch = System::cpu_arch();
Self {
system_name,
system_version,
system_kernel_version,
system_arch,
}
}
}
pub struct AppInfo {
app_version: String,
app_core_mode: String,
pub app_startup_time: Instant,
pub app_is_admin: bool,
}
impl Default for AppInfo {
#[inline]
fn default() -> Self {
let app_version = "0.0.0".into();
let app_core_mode = "NotRunning".into();
let app_is_admin = false;
let app_startup_time = Instant::now();
Self {
app_version,
app_core_mode,
app_startup_time,
app_is_admin,
}
}
}
#[derive(Default)]
pub struct Platform {
pub sysinfo: SysInfo,
pub appinfo: AppInfo,
}
impl Debug for Platform {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Platform")
.field("system_name", &self.sysinfo.system_name)
.field("system_version", &self.sysinfo.system_version)
.field("system_kernel_version", &self.sysinfo.system_kernel_version)
.field("system_arch", &self.sysinfo.system_arch)
.field("app_version", &self.appinfo.app_version)
.field("app_core_mode", &self.appinfo.app_core_mode)
.field("app_is_admin", &self.appinfo.app_is_admin)
.finish()
}
}
impl Display for Platform {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}\nIs Admin: {}",
self.sysinfo.system_name,
self.sysinfo.system_version,
self.sysinfo.system_kernel_version,
self.sysinfo.system_arch,
self.appinfo.app_version,
self.appinfo.app_core_mode,
self.appinfo.app_is_admin
)
}
}
impl Platform {
#[inline]
fn new() -> Self {
Self::default()
}
}
#[inline]
fn is_binary_admin() -> bool {
#[cfg(not(windows))]
unsafe {
libc::geteuid() == 0
}
#[cfg(windows)]
Token::with_current_process()
.and_then(|token| token.privilege_level())
.map(|level| level != PrivilegeLevel::NotPrivileged)
.unwrap_or(false)
}
#[inline]
pub fn list_network_interfaces() -> Vec<String> {
let mut networks = Networks::new();
networks.refresh(false);
networks.keys().map(|name| name.to_owned()).collect()
}
#[inline]
pub fn set_app_core_mode<R: Runtime>(app: &tauri::AppHandle<R>, mode: impl Into<String>) {
let platform_spec = app.state::<RwLock<Platform>>();
let mut spec = platform_spec.write();
spec.appinfo.app_core_mode = mode.into();
}
#[inline]
pub fn get_app_uptime<R: Runtime>(app: &tauri::AppHandle<R>) -> Instant {
let platform_spec = app.state::<RwLock<Platform>>();
let spec = platform_spec.read();
spec.appinfo.app_startup_time
}
#[inline]
pub fn is_current_app_handle_admin<R: Runtime>(app: &tauri::AppHandle<R>) -> bool {
let platform_spec = app.state::<RwLock<Platform>>();
let spec = platform_spec.read();
spec.appinfo.app_is_admin
}
#[inline]
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::<R>::new("clash_verge_sysinfo")
// TODO 现在 crate 还不是真正的 tauri 插件,必须由主 lib 自行注册
// TODO 从 clash-verge 中迁移获取系统信息的 commnand 并实现优雅 structure.field 访问
// .invoke_handler(tauri::generate_handler![
// commands::get_system_info,
// commands::get_app_uptime,
// commands::app_is_admin,
// commands::export_diagnostic_info,
// ])
.setup(move |app, _api| {
let app_version = app.package_info().version.to_string();
let is_admin = is_binary_admin();
let mut platform_spec = Platform::new();
platform_spec.appinfo.app_version = app_version;
platform_spec.appinfo.app_is_admin = is_admin;
app.manage(RwLock::new(platform_spec));
Ok(())
})
.build()
}
-4
View File
@@ -1,4 +0,0 @@
files:
- source: /src/locales/en.json
translation: /src/locales
multilingual: 1
View File
+57 -59
View File
@@ -1,81 +1,79 @@
# CONTRIBUTING — i18n
Thank you for considering contributing to our localization work — your help is appreciated.
Thanks for helping localize Clash Verge Rev. This guide reflects the current architecture, where the React frontend and the Tauri backend keep their translation bundles separate. Follow the steps below to keep both sides in sync without stepping on each other.
Quick overview
## Quick workflow
- cvr-i18 is a CLI that helps manage simple top-level JSON locale files:
- Detect duplicated top-level keys
- Find keys missing versus a base file (default: en.json)
- Export missing entries for translators
- Reorder keys to match the base file for predictable diffs
- Operate on a directory or a single file
- Update the language folder under `src/locales/<lang>/`; use `src/locales/en/` as the canonical reference for keys and intent.
- Run `pnpm i18n:format` to align structure (frontend JSON + backend YAML) and `pnpm i18n:types` to refresh generated typings.
- If you touch backend copy, edit the matching YAML file in `crates/clash-verge-i18n/locales/<lang>.yml`.
- Preview UI changes with `pnpm dev` (desktop shell) or `pnpm web:dev` (web only).
- Keep PRs focused and add screenshots whenever layout could be affected by text length.
Get the CLI (No binary provided yet)
## Frontend locale structure
```bash
git clone https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli
cd clash-verge-rev-i18n-cli
cargo install --path .
# or
cargo install --git https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli
Each locale folder mirrors the namespaces under `src/locales/en/`:
```
src/locales/
en/
connections.json
home.json
shared.json
...
index.ts
zh/
...
```
Common commands
- JSON files map to namespaces (for example `home.json` → `home.*`). Keep keys scoped to the file they belong to.
- `shared.json` stores reusable vocabulary (buttons, validations, etc.); feature-specific wording should live in the relevant namespace.
- `index.ts` re-exports a `resources` object that aggregates the namespace JSON files. When adding or removing namespaces, mirror the pattern from `src/locales/en/index.ts`.
- Frontend bundles are lazy-loaded by `src/services/i18n.ts`. Only languages listed in `supportedLanguages` are fetched at runtime, so append new codes there when you add a locale.
- Show help: `cvr-i18`
- Directory (auto-detects `./locales` or `./src/locales`): `cvr-i18 -d /path/to/locales`
- Check duplicates: `cvr-i18 -k`
- Check missing keys: `cvr-i18 -m`
- Export missing keys: `cvr-i18 -m -e ./exports`
- Sort keys to base file: `cvr-i18 -s`
- Use a base file: `cvr-i18 -b base.json`
- Single file: `cvr-i18 -f locales/zh.json`
Because backend translations now live in their own directory, you no longer need to run `pnpm prebuild` just to sync locales—the frontend folder is the sole source of truth for web bundles.
Options (short)
## Tooling for i18n contributors
- `-d, --directory <DIR>`
- `-f, --file <FILE>`
- `-k, --duplicated-key`
- `-m, --missing-key`
- `-e, --export <DIR>`
- `-s, --sort`
- `-b, --base <FILE>`
- `pnpm i18n:format` → `node scripts/cleanup-unused-i18n.mjs --align --apply`. It aligns key ordering, removes unused entries, and keeps all locales in lock-step with English across both JSON and YAML bundles.
- `pnpm i18n:check` performs a dry-run audit of frontend and backend keys. It scans TS/TSX usage plus Rust `t!(...)` calls in `src-tauri/` and `crates/` to spot missing or extra entries.
- `pnpm i18n:types` regenerates `src/types/generated/i18n-keys.ts` and `src/types/generated/i18n-resources.ts`, ensuring TypeScript catches invalid key usage.
- For dynamic keys that the analyzer cannot statically detect, add explicit references in code or update the script whitelist to avoid false positives.
Exit codes
## Backend (Tauri) locale bundles
- `0` — success (no issues)
- `1` — issues found (duplicates/missing)
- `2` — error (IO/parse/runtime)
Native UI strings (tray menu, notifications, dialogs) use `rust-i18n` with YAML bundles stored in `crates/clash-verge-i18n/locales/<lang>.yml`. These files are completely independent from the frontend JSON modules.
How to contribute (recommended steps)
- Keep `en.yml` semantically aligned with the Simplified Chinese baseline (`zh.yml`). Other locales may temporarily copy English if no translation is available yet.
- When a backend feature introduces new strings, update every YAML file to keep the key set consistent. Missing keys fall back to the default language (`zh`), so catching gaps early avoids mixed-language output.
- The same `pnpm i18n:check` / `pnpm i18n:format` tooling now validates backend YAML keys against Rust usage, so run it after backend i18n edits.
- Rust code resolves the active language through the `clash-verge-i18n` crate (`crates/clash-verge-i18n/src/lib.rs`). No additional build step is required after editing YAML files; `tauri dev` and `tauri build` pick them up automatically.
- Start small: fix typos, improve phrasing, or refine tone and consistency.
- Run the CLI against your locale files to detect duplicates or missing keys.
- Export starter JSONs for translators with `-m -e <DIR>`.
- Prefer incremental PRs or draft PRs; leave a comment on the issue if you want guidance.
- Open an issue to report missing strings, UI context, or localization bugs.
- Add or improve docs and tests to make future contributions easier.
## Adding a new language
PR checklist
1. Duplicate `src/locales/en/` into `src/locales/<new-lang>/` and translate the JSON files while preserving key structure.
2. Update the locale’s `index.ts` to import every namespace. Matching the English file is the easiest way to avoid missing exports.
3. Append the language code to `supportedLanguages` in `src/services/i18n.ts`.
4. If the backend should expose the language, create `crates/clash-verge-i18n/<new-lang>.yml` and translate the keys used in existing YAML files.
5. Run `pnpm i18n:format`, `pnpm i18n:types`, and (optionally) `pnpm i18n:check` in dry-run mode to confirm structure.
- Keep JSON files UTF-8 encoded.
- Follow the repo’s locale file structure and naming conventions.
- Reorder keys to match the base file (`-s`) for minimal diffs.
- Test translations in a local dev build before opening a PR.
- Reference related issues and explain any context for translations or changes.
## Authoring guidelines
Notes
- **Reuse shared vocabulary** before introducing new phrases—check `shared.json` for common actions, statuses, and labels.
- **Prefer semantic keys** (`systemProxy`, `updateInterval`, `autoRefresh`) over positional ones (`item1`, `dialogTitle2`).
- **Document placeholders** using `{{placeholder}}` and ensure components supply the required values.
- **Group keys by UI responsibility** inside each namespace (`page`, `sections`, `forms`, `actions`, `tooltips`, `notifications`, `errors`, `tables`, `statuses`, etc.).
- **Keep strings concise** to avoid layout issues. If a translation needs more context, leave a PR note so reviewers can verify the UI.
- The tool expects simple top-level JSON key/value maps.
- Exported JSONs are starter files for translators (fill in values, keep keys).
- Sorting keeps diffs consistent and reviewable.
## Testing & QA
Repository
https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli
- Launch the desktop shell with `pnpm dev` (or `pnpm web:dev`) and navigate through the affected views to confirm translations load and layouts behave.
- Run `pnpm test` if you touched code that consumes translations or adjusts formatting logic.
- For backend changes, trigger the relevant tray actions or notifications to verify the updated copy.
- Note any remaining untranslated sections or layout concerns in your PR description so maintainers can follow up.
## Feedback & Contributions
## Feedback & support
- For tool usage issues or feedback: please open an Issue in the [repository](https://github.com/clash-verge-rev/clash-verge-rev-i18n-cli) so it can be tracked and addressed.
- For localization contributions (translations, fixes, context notes, etc.): submit a PR or Issue in this repository and include examples, context, and testing instructions when possible.
- If you need help or a review, leave a comment on your submission requesting assistance.
- File an issue for missing context, tooling bugs, or localization gaps so we can track them.
- PRs that touch UI should include screenshots or GIFs whenever text length may affect layout.
- Mention the commands you ran (formatting, type generation, tests) in the PR checklist. If you need extra context or review help, request it via a PR comment.
+82 -1
View File
@@ -1,6 +1,84 @@
## v2.4.4
- **Mihomo(Meta) 内核升级至 v1.19.17**
### 🐞 修复问题
- Linux 无法切换 TUN 堆栈
- macOS service 启动项显示名称(试验性修改)
- macOS 非预期 Tproxy 端口设置
- 流量图缩放异常
- PAC 自动代理脚本内容无法动态调整
- 兼容从旧版服务模式升级
- Monaco 编辑器的行数上限
- 已删除节点在手动分组中导致配置无法加载
- 仪表盘与托盘状态不同步
- 彻底修复 macOS 连接页面显示异常
- windows 端监听关机信号失败
- 修复代理按钮和高亮状态不同步
- 修复侧边栏可能的未能正确跳转
- 修复解锁测试部分地区图标编码不正确
- 修复 IP 检测切页后强制刷新,改为仅在必要时更新
- 修复在搜索框输入不完整正则直接崩溃
- 修复创建窗口时在非简体中文环境或深色主题下的短暂闪烁
- 修复更新时加载进度条异常
- 升级内核失败导致内核不可用问题
- 修复 macOS 在安装和卸载服务时提示与操作不匹配
- 修复菜单排序模式拖拽异常
- 修复托盘菜单代理组前的异常勾选状态
- 修复 Windows 下自定义标题栏按钮在最小化 / 关闭后 hover 状态残留
- 修复直接覆盖 `config.yaml` 使用时无法展开代理组
- 修复 macOS 下应用启动时系统托盘图标颜色闪烁
- 修复应用静默启动模式下非全局热键一直抢占其他应用按键问题
- 修复首页当前节点卡片按延迟排序时,打开节点列表后,`timeout` 节点被排在正常节点前的问题
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
- 支持连接页面各个项目的排序
- 实现可选的自动备份
- 连接页面支持查看已关闭的连接(最近最多 500 个已关闭连接)
- 日志页面支持按时间倒序
- 增加「重新激活订阅」的全局快捷键
- WebView2 Runtime 修复构建升级到 133.0.3065.92
- 侧边栏右键新增「恢复默认排序」
- Linux 下新增对 TUN 「自动重定向」(`auto-redirect` 字段)的配置支持,默认关闭
</details>
<details>
<summary><strong> 🚀 优化改进 </strong></summary>
- 网络请求改为使用 rustls,提升 TLS 兼容性
- rustls 避免因服务器证书链配置问题或较新 TLS 要求导致订阅无法导入
- 替换前端信息编辑组件,提供更好性能
- 优化后端内存和性能表现
- 防止退出时可能的禁用 TUN 失败
- 全新 i18n 支持方式
- 优化备份设置布局
- 优化流量图性能表现,实现动态 FPS 和窗口失焦自动暂停
- 性能优化系统状态获取
- 优化托盘菜单当前订阅检测逻辑
- 优化连接页面表格渲染
- 优化链式代理 UI 反馈
- 优化重启应用的资源清理逻辑
- 优化前端数据刷新
- 优化流量采样和数据处理
- 优化应用重启/退出时的资源清理性能, 大幅缩短执行时间
- 优化前端 WebSocket 连接机制
- 改进旧版 Service 需要重新安装检测流程
- 优化 macOS, Linux 和 Windows 系统信号处理
- 链式代理仅显示 Selector 类型规则组
- 优化 Windows 系统代理设置,不再依赖 `sysproxy.exe` 来设置代理
</details>
## v2.4.3
感谢 @Slinetrac, @oomeow, @Lythrilla, @Dragon1573 的出色贡献
**发行代号:澜**
代号释义:澜象征平稳与融合,本次版本聚焦稳定性、兼容性、性能与体验优化,全面提升整体可靠性。
特别感谢 @Slinetrac, @oomeow, @Lythrilla, @Dragon1573 的出色贡献
### 🐞 修复问题
@@ -36,6 +114,8 @@
- 修复 macOS 从 Dock 栏退出轻量模式状态不同步
- 修复 Linux 系统主题切换不生效
- 修复 `允许自动更新` 字段使手动订阅刷新失效
- 修复轻量模式托盘状态不同步
- 修复一键导入订阅导致应用卡死崩溃的问题
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
@@ -86,6 +166,7 @@
- 在 macOS 10.15 及更高版本默认包含 Mihomo-go122,以解决 Intel 架构 Mac 无法运行内核的问题
- Tun 模式不可用时,禁用系统托盘的 Tun 模式菜单
- 改进订阅更新方式,仍失败需打开订阅设置 `允许危险证书`
- 允许设置 Mihomo 端口范围 1000(含) - 65536(含)
</details>
+19 -11
View File
@@ -15,7 +15,9 @@ A Clash Meta GUI built with <a href="https://github.com/tauri-apps/tauri">Tauri<
<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_ja.md">日本語</a> ·
<a href="./README_ko.md">한국어</a> ·
<a href="./README_fa.md">فارسی</a>
</p>
## Preview
@@ -49,17 +51,23 @@ Join [@clash_verge_rev](https://t.me/clash_verge_re) for update announcements.
## Promotion
#### [Doggygo VPN — Performance-oriented global accelerator](https://verge.dginv.click/#/register?code=oaxsAGo6)
### ✈️ [Doggygo VPN — A Technical-Grade Proxy Service](https://verge.dginv.click/#/register?code=oaxsAGo6)
- High-performance overseas network service with free trials, discounted plans, streaming unlocks, and first-class Hysteria protocol support.
- Register through the exclusive Clash Verge link to get a 3-day trial with 1 GB of traffic per day: [Sign up](https://verge.dginv.click/#/register?code=oaxsAGo6)
- Exclusive 20% off coupon for Clash Verge users: `verge20` (limited to 500 uses)
- Discounted bundle from ¥15.8 per month for 160 GB, plus an additional 20% off for yearly billing
- Operated by an overseas team with reliable service and up to 50% revenue share
- Load-balanced clusters with high-speed dedicated routes (compatible with legacy clients), exceptionally low latency, smooth 4K playback
- First global provider to support the `Hysteria2` protocol—perfect fit for the Clash Verge client
- Supports streaming services and ChatGPT access
- Official site: [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
🚀 A high-performance, overseas, technical-grade proxy service offering free trials and discounted plans, fully unlocking streaming platforms and AI services. The world’s first provider to adopt the **QUIC protocol**.
🎁 Register via the **Clash Verge exclusive invitation link** to receive **3 days of free trial**, with **1GB traffic per day**: 👉 [Register here](https://verge.dginv.click/#/register?code=oaxsAGo6)
#### **Core Advantages:**
- 📱 Self-developed iOS client (the industry’s “only one”), with technology proven in production and **significant ongoing R&D investment**
- 🧑‍💻 **12-hour live customer support** (also assists with Clash Verge usage issues)
- 💰 Discounted plans at **only CNY 21 per month, 160GB traffic, 20% off with annual billing**
- 🌍 Overseas team, no risk of shutdown or exit scams, with up to **50% referral commission**
- ⚙️ **Cluster-based load balancing** architecture with **real-time load monitoring and elastic scaling**, high-speed dedicated lines (compatible with legacy clients), ultra-low latency, unaffected by peak hours, **4K streaming loads instantly**
- ⚡ The world’s first **QUIC-protocol-based proxy service**, now upgraded with the faster **Tuic protocol** (best paired with the Clash Verge client)
- 🎬 Unlocks **streaming platforms and mainstream AI services**
🌐 Official Website: 👉 [https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
#### Build Infrastructure Sponsor — [YXVM Dedicated Servers](https://yxvm.com/aff.php?aff=827)
+3 -1
View File
@@ -15,7 +15,9 @@ Una interfaz gráfica para Clash Meta construida con <a href="https://github.com
<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_ja.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) مراجعه کنید.
+3 -1
View File
@@ -15,7 +15,9 @@
<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_ja.md">日本語</a> ·
<a href="./README_ko.md">한국어</a> ·
<a href="./README_fa.md">فارسی</a>
</p>
## プレビュー
+126
View File
@@ -0,0 +1,126 @@
<h1 align="center">
<img src="../src-tauri/icons/icon.png" alt="Clash" width="128" />
<br>
<a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>의 후속 프로젝트
<br>
</h1>
<h3 align="center">
<a href="https://github.com/tauri-apps/tauri">Tauri</a>로 제작된 Clash Meta GUI.
</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>
## 미리보기
| 다크 | 라이트 |
| ------------------------------------ | --------------------------------------- |
| ![다크 미리보기](./preview_dark.png) | ![라이트 미리보기](./preview_light.png) |
## 설치
[릴리스 페이지](https://github.com/clash-verge-rev/clash-verge-rev/releases)에서 사용 중인 플랫폼에 맞는 설치 프로그램을 다운로드하세요.<br>
Windows (x64/x86), Linux (x64/arm64), macOS 10.15+ (Intel/Apple)을 지원합니다.
#### 릴리스 채널 선택
| 채널 | 설명 | 링크 |
| :---------- | :----------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
| Stable | 안정 릴리스. 신뢰성이 높아 일상 사용에 적합합니다. | [Release](https://github.com/clash-verge-rev/clash-verge-rev/releases) |
| Alpha (EOL) | 퍼블리시 파이프라인 검증에 사용되었던 구 테스트 채널입니다. | [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) |
#### 설치 가이드 및 FAQ
설치 방법, 트러블슈팅, 자주 묻는 질문은 [프로젝트 문서](https://clash-verge-rev.github.io/)를 참고하세요.
---
### 텔레그램 채널
업데이트 공지는 [@clash_verge_rev](https://t.me/clash_verge_re)에서 확인하세요.
## 프로모션
#### [Doggygo VPN — 고성능 글로벌 가속기](https://verge.dginv.click/#/register?code=oaxsAGo6)
- 무료 체험, 할인 요금제, 스트리밍 해제, 선도적인 Hysteria 프로토콜 지원을 갖춘 고성능 해외 네트워크 서비스
- Clash Verge 전용 초대 링크로 가입 시 3일간 매일 1GB 무료 체험 제공: [가입하기](https://verge.dginv.click/#/register?code=oaxsAGo6)
- Clash Verge 전용 20% 할인 코드: `verge20` (선착순 500회)
- 월 15.8위안부터 160GB 제공, 연간 결제 시 추가 20% 할인
- 해외 팀 운영, 높은 신뢰성, 최대 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가 아닌 베어메탈 자원으로 최대 성능 제공
- 🧠 프록시 워크로드, Web/CDN 호스팅, CI/CD, 고부하 작업에 적합
- 💡 CN2 / IEPL 등 다양한 데이터센터 옵션, 즉시 사용 가능
- 📦 본 프로젝트가 사용하는 구성도 판매 중 — 동일한 환경을 사용할 수 있습니다
- 🎯 동일한 빌드 환경이 필요하다면 [지금 YXVM 서버 주문](https://yxvm.com/aff.php?aff=827)
## 기능
- 고성능 Rust와 Tauri 2 프레임워크 기반 데스크톱 앱
- 내장 [Clash.Meta (mihomo)](https://github.com/MetaCubeX/mihomo) 코어, `Alpha` 채널 전환 지원
- 테마 색상, 프록시 그룹/트레이 아이콘, `CSS Injection` 등 세련된 UI 커스터마이징
- 프로필 관리(병합 및 스크립트 보조), 구성 문법 힌트 제공
- 시스템 프록시 제어, 가드 모드, `TUN`(가상 네트워크 어댑터) 지원
- 노드/규칙 시각 편집기
- WebDAV 기반 설정 백업 및 동기화
### FAQ
플랫폼별 가이드는 [FAQ 페이지](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
```
## 기여
Issue와 Pull Request를 환영합니다!
## 감사의 말
Clash Verge Rev는 다음 프로젝트에 기반하거나 영향을 받았습니다:
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): Windows / macOS / Linux용 Tauri 기반 Clash GUI
- [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): Windows / macOS용 Clash GUI
- [vitejs/vite](https://github.com/vitejs/vite): 차세대 프론트엔드 툴링, 매우 빠른 DX
## 라이선스
GPL-3.0 라이선스. 자세한 내용은 [LICENSE](../LICENSE)를 참고하세요.
+3 -1
View File
@@ -15,7 +15,9 @@ Clash Meta GUI базируется на <a href="https://github.com/tauri-apps/
<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_ja.md">日本語</a> ·
<a href="./README_ko.md">한국어</a> ·
<a href="./README_fa.md">فارسی</a>
</p>
## Предпросмотр
+14
View File
@@ -5,6 +5,7 @@ import configPrettier from "eslint-config-prettier";
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
import pluginImportX from "eslint-plugin-import-x";
import pluginPrettier from "eslint-plugin-prettier";
import pluginReactCompiler from "eslint-plugin-react-compiler";
import pluginReactHooks from "eslint-plugin-react-hooks";
import pluginReactRefresh from "eslint-plugin-react-refresh";
import pluginUnusedImports from "eslint-plugin-unused-imports";
@@ -17,7 +18,9 @@ export default defineConfig([
plugins: {
js: eslintJS,
// @ts-expect-error -- https://github.com/typescript-eslint/typescript-eslint/issues/11543
"react-hooks": pluginReactHooks,
"react-compiler": pluginReactCompiler,
// @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/421
"import-x": pluginImportX,
"react-refresh": pluginReactRefresh,
@@ -51,6 +54,7 @@ export default defineConfig([
// React
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
"react-compiler/react-compiler": "error",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
@@ -132,4 +136,14 @@ export default defineConfig([
"prettier/prettier": "warn",
},
},
{
files: ["scripts/**/*.{js,mjs,cjs}", "scripts-workflow/**/*.{js,mjs,cjs}"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
]);
+64 -58
View File
@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.4.3",
"version": "2.4.5-rc.1",
"license": "GPL-3.0-only",
"scripts": {
"prepare": "husky || true",
@@ -24,14 +24,14 @@
"release:autobuild": "pnpm release-version autobuild",
"release:deploytest": "pnpm release-version deploytest",
"publish-version": "node scripts/publish-version.mjs",
"fmt": "cargo fmt --manifest-path ./src-tauri/Cargo.toml",
"clippy": "cargo clippy --all-features --all-targets --manifest-path ./src-tauri/Cargo.toml",
"lint": "eslint -c eslint.config.ts --max-warnings=0 --cache --cache-location .eslintcache src",
"lint:fix": "eslint -c eslint.config.ts --max-warnings=0 --cache --cache-location .eslintcache --fix src",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"test": "vitest run"
"i18n:check": "node scripts/cleanup-unused-i18n.mjs",
"i18n:format": "node scripts/cleanup-unused-i18n.mjs --align --apply",
"i18n:types": "node scripts/generate-i18n-keys.mjs",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@@ -40,97 +40,103 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^7.3.4",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^7.3.7",
"@mui/lab": "7.0.0-beta.17",
"@mui/material": "^7.3.4",
"@mui/x-data-grid": "^8.16.0",
"@tauri-apps/api": "2.9.0",
"@mui/material": "^7.3.7",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.18",
"@tauri-apps/api": "2.9.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.4.4",
"@tauri-apps/plugin-http": "~2.5.4",
"@tauri-apps/plugin-dialog": "^2.5.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-http": "~2.5.5",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "2.3.3",
"@tauri-apps/plugin-shell": "2.3.4",
"@tauri-apps/plugin-updater": "2.9.0",
"@types/json-schema": "^7.0.15",
"ahooks": "^3.9.6",
"axios": "^1.13.1",
"axios": "^1.13.2",
"dayjs": "1.11.19",
"foxact": "^0.2.49",
"i18next": "^25.6.0",
"js-yaml": "^4.1.0",
"json-schema": "^0.4.0",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.54.0",
"foxact": "^0.2.52",
"i18next": "^25.7.4",
"js-yaml": "^4.1.1",
"lodash-es": "^4.17.22",
"monaco-editor": "^0.55.1",
"monaco-yaml": "^5.4.0",
"nanoid": "^5.1.6",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-error-boundary": "6.0.0",
"react-hook-form": "^7.66.0",
"react-i18next": "16.2.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-error-boundary": "6.0.3",
"react-hook-form": "^7.71.0",
"react-i18next": "16.5.2",
"react-markdown": "10.1.0",
"react-monaco-editor": "0.59.0",
"react-router": "^7.9.5",
"react-virtuoso": "^4.14.1",
"swr": "^2.3.6",
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo",
"react-router": "^7.12.0",
"react-virtuoso": "^4.18.1",
"rehype-raw": "^7.0.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.1",
"@eslint/js": "^9.39.0",
"@tauri-apps/cli": "2.9.2",
"@actions/github": "^7.0.0",
"@eslint-react/eslint-plugin": "^2.5.5",
"@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.9.2",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@types/node": "^24.10.7",
"@types/react": "19.2.8",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-react-swc": "^4.2.0",
"@vitejs/plugin-react-swc": "^4.2.2",
"adm-zip": "^0.5.16",
"cli-color": "^2.0.4",
"commander": "^14.0.2",
"cross-env": "^10.1.0",
"eslint": "^9.39.0",
"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-compiler": "19.1.0-rc.2",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-react-refresh": "^0.4.26",
"eslint-plugin-unused-imports": "^4.3.0",
"glob": "^11.0.3",
"globals": "^16.5.0",
"glob": "^13.0.0",
"globals": "^17.0.0",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"jiti": "^2.6.1",
"lint-staged": "^16.2.6",
"meta-json-schema": "^1.19.14",
"lint-staged": "^16.2.7",
"node-fetch": "^3.3.2",
"prettier": "^3.6.2",
"sass": "^1.93.3",
"prettier": "^3.7.4",
"sass": "^1.97.2",
"tar": "^7.5.2",
"terser": "^5.44.0",
"terser": "^5.44.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.2",
"vite": "^7.1.12",
"vite-plugin-monaco-editor-esm": "^2.0.2",
"vite-plugin-svgr": "^4.5.0",
"vitest": "^4.0.6"
"typescript-eslint": "^8.52.0",
"vite": "^7.3.1",
"vite-plugin-svgr": "^4.5.0"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix --max-warnings=0",
"prettier --write",
"git add"
"prettier --write"
],
"*.{css,scss,json,md}": [
"prettier --write",
"git add"
"prettier --write"
]
},
"type": "module",
"packageManager": "pnpm@9.13.2"
"packageManager": "pnpm@10.28.0",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@swc/core",
"core-js",
"es5-ext",
"esbuild",
"unrs-resolver"
]
}
}
+1995 -3349
View File
File diff suppressed because it is too large Load Diff
+15 -7
View File
@@ -1,7 +1,7 @@
{
"extends": ["config:recommended", ":disableDependencyDashboard"],
"baseBranches": ["dev"],
"enabledManagers": ["cargo", "npm"],
"enabledManagers": ["cargo", "npm", "github-actions"],
"labels": ["dependencies"],
"ignorePaths": [
"**/node_modules/**",
@@ -11,14 +11,17 @@
"**/test/**",
"**/tests/**",
"**/__fixtures__/**",
"**/crate/**",
"shared/**"
],
"rangeStrategy": "bump",
"rangeStrategy": "replace",
"packageRules": [
{
"semanticCommitType": "chore",
"matchPackageNames": ["*"]
"matchUpdateTypes": ["patch"],
"automerge": true
},
{
"matchPackageNames": ["*"],
"semanticCommitType": "chore"
},
{
"description": "Disable node/pnpm version updates",
@@ -42,6 +45,11 @@
"groupName": "github actions"
}
],
"postUpdateOptions": ["pnpmDedupe", "updateCargoLock"],
"ignoreDeps": ["criterion"]
"postUpdateOptions": ["pnpmDedupe"],
"ignoreDeps": ["criterion"],
"lockFileMaintenance": {
"enabled": true,
"description": "Force update lockfile to track latest commits of git dependencies",
"schedule": ["before 5am on monday"]
}
}
+3
View File
@@ -0,0 +1,3 @@
[toolchain]
channel = "1.91.0"
components = ["rustfmt", "clippy"]
+2 -2
View File
@@ -1,4 +1,4 @@
max_width = 100
max_width = 120
hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
@@ -6,7 +6,7 @@ use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2021"
edition = "2024"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail
# bump_changelog.sh
# - prepend ./Changelog.md to ./docs/Changelog.history.md
# - overwrite ./Changelog.md with ./template/Changelog.md
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT_DIR"
CHANGELOG="Changelog.md"
HISTORY="docs/Changelog.history.md"
TEMPLATE="template/Changelog.md"
timestamp() { date +"%Y%m%d%H%M%S"; }
echo "Repo root: $ROOT_DIR"
if [ ! -f "$CHANGELOG" ]; then
echo "Error: $CHANGELOG not found" >&2
exit 2
fi
if [ ! -f "$TEMPLATE" ]; then
echo "Error: $TEMPLATE not found" >&2
exit 3
fi
BACKUP_DIR=".changelog_backups"
mkdir -p "$BACKUP_DIR"
bak_ts=$(timestamp)
cp "$CHANGELOG" "$BACKUP_DIR/Changelog.md.bak.$bak_ts"
echo "Backed up $CHANGELOG -> $BACKUP_DIR/Changelog.md.bak.$bak_ts"
if [ -f "$HISTORY" ]; then
cp "$HISTORY" "$BACKUP_DIR/Changelog.history.md.bak.$bak_ts"
echo "Backed up $HISTORY -> $BACKUP_DIR/Changelog.history.md.bak.$bak_ts"
fi
# Prepend current Changelog.md content to top of docs/Changelog.history.md
tmpfile=$(mktemp)
{
cat "$CHANGELOG"
echo
echo ""
if [ -f "$HISTORY" ]; then
cat "$HISTORY"
fi
} > "$tmpfile"
mv "$tmpfile" "$HISTORY"
echo "Prepended $CHANGELOG -> $HISTORY"
# Overwrite Changelog.md with template
cp "$TEMPLATE" "$CHANGELOG"
echo "Overwrote $CHANGELOG with $TEMPLATE"
echo "Done. Backups saved under $BACKUP_DIR"
exit 0
-102
View File
@@ -1,102 +0,0 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const LOCALES_DIR = path.resolve(__dirname, "../src/locales");
const SRC_DIRS = [
path.resolve(__dirname, "../src"),
path.resolve(__dirname, "../src-tauri"),
];
const exts = [".js", ".ts", ".tsx", ".jsx", ".vue", ".rs"];
// 递归获取所有文件
function getAllFiles(dir, exts) {
let files = [];
fs.readdirSync(dir).forEach((file) => {
const full = path.join(dir, file);
if (fs.statSync(full).isDirectory()) {
files = files.concat(getAllFiles(full, exts));
} else if (exts.includes(path.extname(full))) {
files.push(full);
}
});
return files;
}
// 读取所有源码内容为一个大字符串
function getAllSourceContent() {
const files = SRC_DIRS.flatMap((dir) => getAllFiles(dir, exts));
return files.map((f) => fs.readFileSync(f, "utf8")).join("\n");
}
// 白名单 key,不检查这些 key 是否被使用
const WHITELIST_KEYS = [
"theme.light",
"theme.dark",
"theme.system",
"Already Using Latest Core Version",
];
// 主流程
function processI18nFile(i18nPath, lang, allSource) {
const i18n = JSON.parse(fs.readFileSync(i18nPath, "utf8"));
const keys = Object.keys(i18n);
const used = {};
const unused = [];
let checked = 0;
const total = keys.length;
keys.forEach((key) => {
if (WHITELIST_KEYS.includes(key)) {
used[key] = i18n[key];
} else {
// 只查找一次
const regex = new RegExp(`["'\`]${key}["'\`]`);
if (regex.test(allSource)) {
used[key] = i18n[key];
} else {
unused.push(key);
}
}
checked++;
if (checked % 20 === 0 || checked === total) {
const percent = ((checked / total) * 100).toFixed(1);
process.stdout.write(
`\r[${lang}] Progress: ${checked}/${total} (${percent}%)`,
);
if (checked === total) process.stdout.write("\n");
}
});
// 输出未使用的 key
console.log(`\n[${lang}] Unused keys:`, unused);
// 备份原文件
const oldPath = i18nPath + ".old";
fs.renameSync(i18nPath, oldPath);
// 写入精简后的 i18n 文件(保留原文件名)
fs.writeFileSync(i18nPath, JSON.stringify(used, null, 2), "utf8");
console.log(
`[${lang}] Cleaned i18n file written to src/locales/${path.basename(i18nPath)}`,
);
console.log(`[${lang}] Original file backed up as ${path.basename(oldPath)}`);
}
function main() {
// 支持 zhtw.json、zh-tw.json、zh_CN.json 等
const files = fs
.readdirSync(LOCALES_DIR)
.filter((f) => /^[a-z0-9\-_]+\.json$/i.test(f) && !f.endsWith(".old"));
const allSource = getAllSourceContent();
files.forEach((file) => {
const lang = path.basename(file, ".json");
processI18nFile(path.join(LOCALES_DIR, file), lang, allSource);
});
}
main();
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
#
# extract_update_logs.sh
# 从 Changelog.md 提取最新版本 (## v...) 的更新内容
# 并输出到屏幕或写入环境变量文件(如 GitHub Actions)
set -euo pipefail
CHANGELOG_FILE="Changelog.md"
if [[ ! -f "$CHANGELOG_FILE" ]]; then
echo "❌ 文件不存在: $CHANGELOG_FILE" >&2
exit 1
fi
# 提取从第一个 '## v' 开始到下一个 '## v' 前的内容
UPDATE_LOGS=$(awk '
/^## v/ {
if (found) exit;
found=1
}
found
' "$CHANGELOG_FILE")
if [[ -z "$UPDATE_LOGS" ]]; then
echo "⚠️ 未找到更新日志内容"
exit 0
fi
echo "✅ 提取到的最新版本日志内容如下:"
echo "----------------------------------------"
echo "$UPDATE_LOGS"
echo "----------------------------------------"
# 如果在 GitHub Actions 环境中(GITHUB_ENV 已定义)
if [[ -n "${GITHUB_ENV:-}" ]]; then
{
echo "UPDATE_LOGS<<EOF"
echo "$UPDATE_LOGS"
echo "EOF"
} >> "$GITHUB_ENV"
echo "✅ 已写入 GitHub 环境变量 UPDATE_LOGS"
fi
+1 -1
View File
@@ -1,7 +1,7 @@
import { exec } from "child_process";
import { promisify } from "util";
import fs from "fs/promises";
import path from "path";
import { promisify } from "util";
/**
* 为Alpha版本重命名版本号
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env node
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, "..");
const LOCALE_DIR = path.resolve(ROOT_DIR, "src/locales/en");
const KEY_OUTPUT = path.resolve(ROOT_DIR, "src/types/generated/i18n-keys.ts");
const RESOURCE_OUTPUT = path.resolve(
ROOT_DIR,
"src/types/generated/i18n-resources.ts",
);
const isPlainObject = (value) =>
typeof value === "object" && value !== null && !Array.isArray(value);
const flattenKeys = (data, prefix = "") => {
const keys = [];
for (const [key, value] of Object.entries(data)) {
const nextPrefix = prefix ? `${prefix}.${key}` : key;
if (isPlainObject(value)) {
keys.push(...flattenKeys(value, nextPrefix));
} else {
keys.push(nextPrefix);
}
}
return keys;
};
const buildType = (data, indent = 0) => {
if (!isPlainObject(data)) {
return "string";
}
const entries = Object.entries(data).sort(([a], [b]) => a.localeCompare(b));
const pad = " ".repeat(indent);
const inner = entries
.map(([key, value]) => {
const typeStr = buildType(value, indent + 2);
return `${" ".repeat(indent + 2)}${JSON.stringify(key)}: ${typeStr};`;
})
.join("\n");
return entries.length
? `{
${inner}
${pad}}`
: "{}";
};
const loadNamespaceJson = async () => {
const dirents = await fs.readdir(LOCALE_DIR, { withFileTypes: true });
const namespaces = [];
for (const dirent of dirents) {
if (!dirent.isFile() || !dirent.name.endsWith(".json")) continue;
const name = dirent.name.replace(/\.json$/, "");
const filePath = path.join(LOCALE_DIR, dirent.name);
const raw = await fs.readFile(filePath, "utf8");
const json = JSON.parse(raw);
namespaces.push({ name, json });
}
namespaces.sort((a, b) => a.name.localeCompare(b.name));
return namespaces;
};
const buildKeysFile = (keys) => {
const arrayLiteral = keys.map((key) => ` "${key}"`).join(",\n");
return `// This file is auto-generated by scripts/generate-i18n-keys.mjs\n// Do not edit this file manually.\n\nexport const translationKeys = [\n${arrayLiteral}\n] as const;\n\nexport type TranslationKey = typeof translationKeys[number];\n`;
};
const buildResourcesFile = (namespaces) => {
const namespaceEntries = namespaces
.map(({ name, json }) => {
const typeStr = buildType(json, 4);
return ` ${JSON.stringify(name)}: ${typeStr};`;
})
.join("\n");
return `// This file is auto-generated by scripts/generate-i18n-keys.mjs\n// Do not edit this file manually.\n\nexport interface TranslationResources {\n translation: {\n${namespaceEntries}\n };\n}\n`;
};
const main = async () => {
const namespaces = await loadNamespaceJson();
const keys = namespaces.flatMap(({ name, json }) => flattenKeys(json, name));
const keysContent = buildKeysFile(keys);
const resourcesContent = buildResourcesFile(namespaces);
await fs.mkdir(path.dirname(KEY_OUTPUT), { recursive: true });
await fs.writeFile(KEY_OUTPUT, keysContent, "utf8");
await fs.writeFile(RESOURCE_OUTPUT, resourcesContent, "utf8");
console.log(`Generated ${keys.length} translation keys.`);
};
main().catch((error) => {
console.error("Failed to generate i18n metadata:", error);
process.exitCode = 1;
});
+8 -7
View File
@@ -1,9 +1,10 @@
import fs from "fs";
import fsp from "fs/promises";
import path from "path";
import AdmZip from "adm-zip";
import { createRequire } from "module";
import { getOctokit, context } from "@actions/github";
import path from "path";
import { context, getOctokit } from "@actions/github";
import AdmZip from "adm-zip";
const target = process.argv.slice(2)[0];
const alpha = process.argv.slice(2)[1];
@@ -49,9 +50,9 @@ async function resolvePortable() {
zip.addLocalFolder(
path.join(
releaseDir,
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
`Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${arch}`,
),
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
`Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${arch}`,
);
zip.addLocalFolder(configDir, ".config");
@@ -79,11 +80,11 @@ async function resolvePortable() {
tag,
});
let assets = release.assets.filter((x) => {
const assets = release.assets.filter((x) => {
return x.name === zipFile;
});
if (assets.length > 0) {
let id = assets[0].id;
const id = assets[0].id;
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: id,
+4 -3
View File
@@ -1,8 +1,9 @@
import fs from "fs";
import path from "path";
import AdmZip from "adm-zip";
import { createRequire } from "module";
import fsp from "fs/promises";
import { createRequire } from "module";
import path from "path";
import AdmZip from "adm-zip";
const target = process.argv.slice(2)[0];
const ARCH_MAP = {
+16 -49
View File
@@ -1,14 +1,16 @@
import AdmZip from "adm-zip";
import { execSync } from "child_process";
import { createHash } from "crypto";
import fs from "fs";
import fsp from "fs/promises";
import path from "path";
import zlib from "zlib";
import AdmZip from "adm-zip";
import { glob } from "glob";
import { HttpsProxyAgent } from "https-proxy-agent";
import fetch from "node-fetch";
import path from "path";
import { extract } from "tar";
import zlib from "zlib";
import { log_debug, log_error, log_info, log_success } from "./utils.mjs";
/**
@@ -55,7 +57,7 @@ const ARCH_MAP = {
const arg1 = process.argv.slice(2)[0];
const arg2 = process.argv.slice(2)[1];
let target = arg1 === "--force" || arg1 === "-f" ? arg2 : arg1;
const target = arg1 === "--force" || arg1 === "-f" ? arg2 : arg1;
const { platform, arch } = target
? { platform: PLATFORM_MAP[target], arch: ARCH_MAP[target] }
: process;
@@ -113,7 +115,7 @@ async function calculateFileHash(filePath) {
const hashSum = createHash("sha256");
hashSum.update(fileBuffer);
return hashSum.digest("hex");
} catch (err) {
} catch (ignoreErr) {
return null;
}
}
@@ -547,9 +549,9 @@ const resolveServicePermission = async () => {
const hashCache = await loadHashCache();
let hasChanges = false;
for (let f of serviceExecutables) {
for (const f of serviceExecutables) {
const files = glob.sync(path.join(resDir, f));
for (let filePath of files) {
for (const filePath of files) {
if (fs.existsSync(filePath)) {
const currentHash = await calculateFileHash(filePath);
const cacheKey = `${filePath}_chmod`;
@@ -573,52 +575,29 @@ const resolveServicePermission = async () => {
}
};
// resolve locales (从 src/locales 复制到 resources/locales,并使用 hash 检查)
async function resolveLocales() {
const srcLocalesDir = path.join(cwd, "src/locales");
const targetLocalesDir = path.join(cwd, "src-tauri/resources/locales");
try {
await fsp.mkdir(targetLocalesDir, { recursive: true });
const files = await fsp.readdir(srcLocalesDir);
for (const file of files) {
const srcPath = path.join(srcLocalesDir, file);
const targetPath = path.join(targetLocalesDir, file);
if (!(await hasFileChanged(srcPath, targetPath))) continue;
await fsp.copyFile(srcPath, targetPath);
await updateHashCache(targetPath);
log_success(`Copied locale file: ${file}`);
}
log_success("All locale files processed successfully");
} catch (err) {
log_error("Error copying locale files:", err.message);
throw err;
}
}
// =======================
// Other resource resolvers (service, mmdb, geosite, geoip, enableLoopback, sysproxy)
// Other resource resolvers (service, mmdb, geosite, geoip, enableLoopback)
// =======================
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service-ipc/releases/download/${SIDECAR_HOST}`;
const resolveService = () => {
let ext = platform === "win32" ? ".exe" : "";
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
const ext = platform === "win32" ? ".exe" : "";
const suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
return resolveResource({
file: "clash-verge-service" + suffix + ext,
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
});
};
const resolveInstall = () => {
let ext = platform === "win32" ? ".exe" : "";
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
const ext = platform === "win32" ? ".exe" : "";
const suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
return resolveResource({
file: "clash-verge-service-install" + suffix + ext,
downloadURL: `${SERVICE_URL}/clash-verge-service-install${ext}`,
});
};
const resolveUninstall = () => {
let ext = platform === "win32" ? ".exe" : "";
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
const ext = platform === "win32" ? ".exe" : "";
const suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
return resolveResource({
file: "clash-verge-service-uninstall" + suffix + ext,
downloadURL: `${SERVICE_URL}/clash-verge-service-uninstall${ext}`,
@@ -645,11 +624,6 @@ const resolveEnableLoopback = () =>
file: "enableLoopback.exe",
downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`,
});
const resolveWinSysproxy = () =>
resolveResource({
file: "sysproxy.exe",
downloadURL: `https://github.com/clash-verge-rev/sysproxy/releases/download/${arch}/sysproxy.exe`,
});
const resolveSetDnsScript = () =>
resolveResource({
@@ -697,12 +671,6 @@ const tasks = [
retry: 5,
unixOnly: platform === "linux" || platform === "darwin",
},
{
name: "windows-sysproxy",
func: resolveWinSysproxy,
retry: 5,
winOnly: true,
},
{
name: "set_dns_script",
func: resolveSetDnsScript,
@@ -715,7 +683,6 @@ const tasks = [
retry: 5,
macosOnly: true,
},
{ name: "locales", func: resolveLocales, retry: 2 },
];
async function runTask() {
+2 -1
View File
@@ -30,10 +30,11 @@
*/
import { execSync } from "child_process";
import { program } from "commander";
import fs from "fs/promises";
import path from "path";
import { program } from "commander";
/**
* 获取当前 git 短 commit hash
* @returns {string}
+4 -3
View File
@@ -39,9 +39,10 @@ function is_valid_ip() {
# 获取网络接口和硬件端口
nic=$(route -n get default | grep "interface" | awk '{print $2}')
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '
/Hardware Port:/{port=$0; gsub("Hardware Port: ", "", port)}
/Device: /{if ($2 == dev) {print port; exit}}
# 从网络服务列表中获取硬件端口
hardware_port=$(networksetup -listnetworkserviceorder | awk -v dev="$nic" '
/^\([0-9]+\) /{port=$0; sub(/^\([0-9]+\) /, "", port)}
/\(Hardware Port:/{interface=$NF;sub(/\)/, "", interface); if (interface == dev) {print port; exit}}
')
# 获取当前DNS设置
+3 -1
View File
@@ -1,5 +1,7 @@
import axios from "axios";
import { readFileSync } from "fs";
import axios from "axios";
import { log_error, log_info, log_success } from "./utils.mjs";
const CHAT_ID_RELEASE = "@clash_verge_re"; // 正式发布频道
+3 -10
View File
@@ -1,16 +1,9 @@
#!/bin/bash
nic=$(route -n get default | grep "interface" | awk '{print $2}')
hardware_port=$(networksetup -listallhardwareports | awk -v dev="$nic" '
/Hardware Port:/{
port=$0; gsub("Hardware Port: ", "", port)
}
/Device: /{
if ($2 == dev) {
print port;
exit
}
}
hardware_port=$(networksetup -listnetworkserviceorder | awk -v dev="$nic" '
/^\([0-9]+\) /{port=$0; sub(/^\([0-9]+\) /, "", port)}
/\(Hardware Port:/{interface=$NF;sub(/\)/, "", interface); if (interface == dev) {print port; exit}}
')
if [ -f .original_dns.txt ]; then
+7 -7
View File
@@ -2,9 +2,9 @@ import fs from "fs";
import fsp from "fs/promises";
import path from "path";
const UPDATE_LOG = "UPDATELOG.md";
const UPDATE_LOG = "Changelog.md";
// parse the UPDATELOG.md
// parse the Changelog.md
export async function resolveUpdateLog(tag) {
const cwd = process.cwd();
@@ -14,7 +14,7 @@ export async function resolveUpdateLog(tag) {
const file = path.join(cwd, UPDATE_LOG);
if (!fs.existsSync(file)) {
throw new Error("could not found UPDATELOG.md");
throw new Error("could not found Changelog.md");
}
const data = await fsp.readFile(file, "utf-8");
@@ -38,7 +38,7 @@ export async function resolveUpdateLog(tag) {
});
if (!map[tag]) {
throw new Error(`could not found "${tag}" in UPDATELOG.md`);
throw new Error(`could not found "${tag}" in Changelog.md`);
}
return map[tag].join("\n").trim();
@@ -49,7 +49,7 @@ export async function resolveUpdateLogDefault() {
const file = path.join(cwd, UPDATE_LOG);
if (!fs.existsSync(file)) {
throw new Error("could not found UPDATELOG.md");
throw new Error("could not found Changelog.md");
}
const data = await fsp.readFile(file, "utf-8");
@@ -58,7 +58,7 @@ export async function resolveUpdateLogDefault() {
const reEnd = /^---/;
let isCapturing = false;
let content = [];
const content = [];
let firstTag = "";
for (const line of data.split("\n")) {
@@ -77,7 +77,7 @@ export async function resolveUpdateLogDefault() {
}
if (!firstTag) {
throw new Error("could not found any version tag in UPDATELOG.md");
throw new Error("could not found any version tag in Changelog.md");
}
return content.join("\n").trim();
+4 -3
View File
@@ -1,5 +1,6 @@
import { context, getOctokit } from "@actions/github";
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog } from "./updatelog.mjs";
const UPDATE_TAG_NAME = "updater";
@@ -35,7 +36,7 @@ async function resolveUpdater() {
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name), // use updatelog.md
notes: await resolveUpdateLog(tag.name), // use Changelog.md
pub_date: new Date().toISOString(),
platforms: {
"windows-x86_64": { signature: "", url: "" },
@@ -113,7 +114,7 @@ async function resolveUpdater() {
});
// delete the old assets
for (let asset of updateRelease.assets) {
for (const asset of updateRelease.assets) {
if (asset.name === UPDATE_JSON_FILE) {
await github.rest.repos.deleteReleaseAsset({
...options,
+3 -2
View File
@@ -1,5 +1,6 @@
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import fetch from "node-fetch";
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
// Add stable update JSON filenames
@@ -259,7 +260,7 @@ async function processRelease(github, options, tag, isAlpha) {
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY;
// Delete existing assets with these names
for (let asset of updateRelease.assets) {
for (const asset of updateRelease.assets) {
if (asset.name === jsonFile) {
await github.rest.repos.deleteReleaseAsset({
...options,
+114 -202
View File
@@ -1,127 +1,18 @@
[package]
name = "clash-verge"
version = "2.4.3"
version = "2.4.5-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"
edition = "2024"
build = "build.rs"
edition = "2024"
rust-version = "1.91"
[package.metadata.bundle]
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "2.5.1", features = [] }
[dependencies]
warp = { version = "0.4.2", features = ["server"] }
anyhow = "1.0.100"
open = "5.3.2"
log = "0.4.28"
dunce = "1.0.5"
nanoid = "0.4"
chrono = "0.4.42"
sysinfo = { version = "0.37.2", features = ["network", "system"] }
boa_engine = "0.21.0"
serde_json = "1.0.145"
serde_yaml_ng = "0.10.0"
once_cell = "1.21.3"
port_scanner = "0.1.5"
delay_timer = "0.11.6"
parking_lot = "0.12.5"
percent-encoding = "2.3.2"
tokio = { version = "1.48.0", features = [
"rt-multi-thread",
"macros",
"time",
"sync",
] }
serde = { version = "1.0.228", features = ["derive"] }
reqwest = { version = "0.12.24", features = ["json", "cookies"] }
regex = "1.12.2"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
tauri = { version = "2.9.2", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
network-interface = { version = "2.0.3", features = ["serde"] }
tauri-plugin-shell = "2.3.3"
tauri-plugin-dialog = "2.4.2"
tauri-plugin-fs = "2.4.4"
tauri-plugin-process = "2.3.1"
tauri-plugin-clipboard-manager = "2.3.2"
tauri-plugin-deep-link = "2.4.5"
tauri-plugin-window-state = "2.4.1"
zip = "6.0.0"
reqwest_dav = "0.2.2"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.3.4"
futures = "0.3.31"
sys-locale = "0.3.2"
libc = "0.2.177"
gethostname = "1.1.0"
scopeguard = "1.2.0"
tauri-plugin-notification = "2.3.3"
tokio-stream = "0.1.17"
isahc = { version = "1.7.2", default-features = false, features = [
"text-decoding",
"parking_lot",
] }
backoff = { version = "0.4.0", features = ["tokio"] }
compact_str = { version = "0.9.0", features = ["serde"] }
tauri-plugin-http = "2.5.4"
flexi_logger = "0.31.7"
console-subscriber = { version = "0.5.0", optional = true }
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"
smartstring = { version = "1.0.1", features = ["serde"] }
clash_verge_service_ipc = { version = "2.0.21", features = [
"client",
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
arc-swap = "1.7.1"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
deelevate = "0.2.0"
winreg = "0.55.0"
winapi = { version = "0.3.9", features = [
"winbase",
"fileapi",
"winnt",
"handleapi",
"errhandlingapi",
"minwindef",
"winerror",
"tlhelp32",
"processthreadsapi",
"winhttp",
"winreg",
] }
windows-sys = { version = "0.61.2", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_System_SystemServices",
"Win32_UI_WindowsAndMessaging",
] }
[target.'cfg(target_os = "linux")'.dependencies]
users = "0.11.0"
[target.'cfg(unix)'.dependencies]
signal-hook = "0.3.18"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.5.1"
tauri-plugin-global-shortcut = "2.3.1"
tauri-plugin-updater = "2.9.0"
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[features]
default = ["custom-protocol"]
@@ -131,98 +22,119 @@ tauri-dev = []
tokio-trace = ["console-subscriber"]
clippy = ["tauri/test"]
tracing = []
tracing-full = []
[[bench]]
name = "draft_benchmark"
path = "benches/draft_benchmark.rs"
harness = false
[package.metadata.bundle]
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[profile.release]
panic = "abort"
codegen-units = 1
lto = "thin"
opt-level = 3
debug = false
strip = true
overflow-checks = false
rpath = false
[build-dependencies]
tauri-build = { version = "2.5.3", features = [] }
[profile.dev]
incremental = true
codegen-units = 64
opt-level = 0
debug = true
strip = "none"
overflow-checks = true
lto = false
rpath = false
[dependencies]
clash-verge-draft = { workspace = true }
clash-verge-logging = { workspace = true }
clash-verge-signal = { workspace = true }
clash-verge-types = { workspace = true }
clash-verge-i18n = { workspace = true }
tauri-plugin-clash-verge-sysinfo = { workspace = true }
tauri-plugin-clipboard-manager = { workspace = true }
tauri = { workspace = true, features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
parking_lot = { workspace = true }
anyhow = { workspace = true }
tokio = { workspace = true }
compact_str = { workspace = true }
flexi_logger = { workspace = true }
log = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_yaml_ng = { workspace = true }
smartstring = { workspace = true, features = ["serde"] }
bitflags = { workspace = true }
warp = { version = "0.4.2", features = ["server"] }
open = "5.3.3"
dunce = "1.0.5"
nanoid = "0.4"
chrono = "0.4.42"
boa_engine = "0.21.0"
once_cell = { version = "1.21.3", features = ["parking_lot"] }
delay_timer = "0.11.6"
percent-encoding = "2.3.2"
reqwest = { version = "0.13.1", features = [
"json",
"cookies",
"rustls",
"form",
] }
regex = "1.12.2"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", features = [
"guard",
] }
network-interface = { version = "2.0.5", features = ["serde"] }
tauri-plugin-shell = "2.3.4"
tauri-plugin-dialog = "2.5.0"
tauri-plugin-fs = "2.4.5"
tauri-plugin-process = "2.3.1"
tauri-plugin-deep-link = "2.4.6"
tauri-plugin-window-state = "2.4.1"
zip = "7.0.0"
reqwest_dav = "0.2.2"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.3.4"
futures = "0.3.31"
gethostname = "1.1.0"
scopeguard = "1.2.0"
tauri-plugin-notification = "2.3.3"
tokio-stream = "0.1.18"
backoff = { version = "0.4.0", features = ["tokio"] }
tauri-plugin-http = "2.5.5"
console-subscriber = { version = "0.5.0", optional = true }
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.29", features = [
"client",
], git = "https://github.com/clash-verge-rev/clash-verge-service-ipc" }
arc-swap = "1.8.0"
rust_iso3166 = "0.1.14"
# Use the git repo until the next release after v2.0.0.
dark-light = { git = "https://github.com/rust-dark-light/dark-light" }
governor = "0.10.4"
[profile.fast-release]
inherits = "release"
codegen-units = 64
incremental = true
lto = false
opt-level = 0
debug = true
strip = false
[target.'cfg(windows)'.dependencies]
deelevate = { workspace = true }
runas = "=1.2.0"
winreg = "0.55.0"
winapi = { version = "0.3.9", features = [
"winbase",
"fileapi",
"winnt",
"handleapi",
"errhandlingapi",
"minwindef",
"winerror",
"stringapiset",
"tlhelp32",
"processthreadsapi",
"winhttp",
"winreg",
"winnls",
] }
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.5.1"
tauri-plugin-global-shortcut = "2.3.1"
tauri-plugin-updater = "2.9.0"
[dev-dependencies]
criterion = { version = "0.7.0", features = ["async_tokio"] }
criterion = { workspace = true }
[lints.clippy]
# Core categories - most important for code safety and correctness
correctness = { level = "deny", priority = -1 }
suspicious = { level = "deny", priority = -1 }
# Critical safety lints - warn for now due to extensive existing usage
unwrap_used = "warn"
expect_used = "warn"
panic = "deny"
unimplemented = "deny"
# Development quality lints
todo = "warn"
dbg_macro = "warn"
#print_stdout = "warn"
#print_stderr = "warn"
# Performance lints for proxy application
clone_on_ref_ptr = "warn"
rc_clone_in_vec_init = "warn"
large_stack_arrays = "warn"
large_const_arrays = "warn"
# Security lints
#integer_division = "warn"
#lossy_float_literal = "warn"
#default_numeric_fallback = "warn"
# Mutex and async lints - strict control
async_yields_async = "deny" # Prevents missing await in async blocks
mutex_atomic = "deny" # Use atomics instead of Mutex<bool/int>
mutex_integer = "deny" # Use AtomicInt instead of Mutex<int>
rc_mutex = "deny" # Single-threaded Rc with Mutex is wrong
unused_async = "deny" # Too many false positives in Tauri/framework code
await_holding_lock = "deny"
large_futures = "deny"
future_not_send = "deny"
# Common style improvements
redundant_else = "deny" # Too many in existing code
needless_continue = "deny" # Too many in existing code
needless_raw_string_hashes = "deny" # Too many in existing code
# Disable noisy categories for existing codebase but keep them available
#style = { level = "allow", priority = -1 }
#complexity = { level = "allow", priority = -1 }
#perf = { level = "allow", priority = -1 }
#pedantic = { level = "allow", priority = -1 }
#nursery = { level = "allow", priority = -1 }
#restriction = { level = "allow", priority = -1 }
or_fun_call = "deny"
cognitive_complexity = "deny"
[lints]
workspace = true
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<array>
<string>io.github.clash-verge-rev.clash-verge-rev.service</string>
</array>
</dict>
</plist>
+17 -48
View File
@@ -1,18 +1,17 @@
use super::CmdResult;
use crate::core::sysopt::Sysopt;
use crate::utils::resolve::ui::{self, UiReadyStage};
use crate::{
cmd::StringifyErr,
feat, logging,
utils::{
dirs::{self, PathBufExec},
logging::Type,
},
cmd::StringifyErr as _,
feat,
utils::dirs::{self, PathBufExec as _},
};
use clash_verge_logging::{Type, logging};
use smartstring::alias::String;
use std::path::Path;
use tauri::{AppHandle, Manager};
use tauri::{AppHandle, Manager as _};
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::io::AsyncWriteExt as _;
/// 打开应用程序所在目录
#[tauri::command]
@@ -83,17 +82,14 @@ pub async fn restart_app() -> CmdResult<()> {
/// 获取便携版标识
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
pub fn get_portable_flag() -> bool {
*dirs::PORTABLE_FLAG.get().unwrap_or(&false)
}
/// 获取应用目录
#[tauri::command]
pub fn get_app_dir() -> CmdResult<String> {
let app_home_dir = dirs::app_home_dir()
.stringify_err()?
.to_string_lossy()
.into();
let app_home_dir = dirs::app_home_dir().stringify_err()?.to_string_lossy().into();
Ok(app_home_dir)
}
@@ -106,10 +102,7 @@ pub fn get_auto_launch_status() -> CmdResult<bool> {
/// 下载图标缓存
#[tauri::command]
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = dirs::app_home_dir()
.stringify_err()?
.join("icons")
.join("cache");
let icon_cache_dir = dirs::app_home_dir().stringify_err()?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(name.as_str());
if icon_path.exists() {
@@ -135,9 +128,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
let content = response.bytes().await.stringify_err()?;
let is_html = content.len() > 15
&& (content.starts_with(b"<!DOCTYPE html")
|| content.starts_with(b"<html")
|| content.starts_with(b"<?xml"));
&& (content.starts_with(b"<!DOCTYPE html") || content.starts_with(b"<html") || content.starts_with(b"<?xml"));
if is_image && !is_html {
{
@@ -240,36 +231,14 @@ pub async fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<Stri
/// 通知UI已准备就绪
#[tauri::command]
pub fn notify_ui_ready() -> CmdResult<()> {
pub fn notify_ui_ready() {
logging!(info, Type::Cmd, "前端UI已准备就绪");
crate::utils::resolve::ui::mark_ui_ready();
Ok(())
ui::mark_ui_ready();
}
/// UI加载阶段
#[tauri::command]
pub fn update_ui_stage(stage: String) -> CmdResult<()> {
logging!(info, Type::Cmd, "UI加载阶段更新: {}", stage.as_str());
use crate::utils::resolve::ui::UiReadyStage;
let stage_enum = match stage.as_str() {
"NotStarted" => UiReadyStage::NotStarted,
"Loading" => UiReadyStage::Loading,
"DomReady" => UiReadyStage::DomReady,
"ResourcesLoaded" => UiReadyStage::ResourcesLoaded,
"Ready" => UiReadyStage::Ready,
_ => {
logging!(
warn,
Type::Cmd,
"Warning: 未知的UI加载阶段: {}",
stage.as_str()
);
return Err(format!("未知的UI加载阶段: {}", stage.as_str()).into());
}
};
crate::utils::resolve::ui::update_ui_ready_stage(stage_enum);
Ok(())
pub fn update_ui_stage(stage: UiReadyStage) {
logging!(info, Type::Cmd, "UI加载阶段更新: {:?}", &stage);
ui::update_ui_ready_stage(stage);
}
+8 -4
View File
@@ -1,5 +1,5 @@
use super::CmdResult;
use crate::{cmd::StringifyErr, feat};
use crate::{cmd::StringifyErr as _, feat};
use feat::LocalBackupFile;
use smartstring::alias::String;
@@ -27,10 +27,14 @@ pub async fn restore_local_backup(filename: String) -> CmdResult<()> {
feat::restore_local_backup(filename).await.stringify_err()
}
/// Import local backup into the app's backup directory
#[tauri::command]
pub async fn import_local_backup(source: String) -> CmdResult<String> {
feat::import_local_backup(source).await.stringify_err()
}
/// Export local backup to a user selected destination
#[tauri::command]
pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {
feat::export_local_backup(filename, destination)
.await
.stringify_err()
feat::export_local_backup(filename, destination).await.stringify_err()
}
+33 -60
View File
@@ -1,12 +1,13 @@
use super::CmdResult;
use crate::feat;
use crate::utils::dirs;
use crate::{
cmd::StringifyErr,
config::Config,
cmd::StringifyErr as _,
config::{ClashInfo, Config},
constants,
core::{CoreManager, handle, validate::CoreConfigValidator},
};
use crate::{config::*, feat, logging, utils::logging::Type};
use clash_verge_logging::{Type, logging, logging_error};
use compact_str::CompactString;
use serde_yaml_ng::Mapping;
use smartstring::alias::String;
@@ -22,13 +23,13 @@ pub async fn copy_clash_env() -> CmdResult {
/// 获取Clash信息
#[tauri::command]
pub async fn get_clash_info() -> CmdResult<ClashInfo> {
Ok(Config::clash().await.latest_ref().get_client_info())
Ok(Config::clash().await.data_arc().get_client_info())
}
/// 修改Clash配置
#[tauri::command]
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
feat::patch_clash(payload).await.stringify_err()
feat::patch_clash(&payload).await.stringify_err()
}
/// 修改Clash模式
@@ -45,21 +46,18 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
match CoreManager::global().change_core(&clash_core).await {
Ok(_) => {
logging_error!(Type::Core, Config::profiles().await.latest_arc().save_file().await);
// 切换内核后重启内核
match CoreManager::global().restart_core().await {
Ok(_) => {
logging!(
info,
Type::Core,
"core changed and restarted to {clash_core}"
);
logging!(info, Type::Core, "core changed and restarted to {clash_core}");
handle::Handle::notice_message("config_core::change_success", clash_core);
handle::Handle::refresh_clash();
Ok(None)
}
Err(err) => {
let error_msg: String =
format!("Core changed but failed to restart: {err}").into();
let error_msg: String = format!("Core changed but failed to restart: {err}").into();
handle::Handle::notice_message("config_core::change_error", error_msg.clone());
logging!(error, Type::Core, "{error_msg}");
Ok(Some(error_msg))
@@ -88,6 +86,7 @@ pub async fn start_core() -> CmdResult {
/// 关闭核心
#[tauri::command]
pub async fn stop_core() -> CmdResult {
logging_error!(Type::Core, Config::profiles().await.latest_arc().save_file().await);
let result = CoreManager::global().stop_core().await.stringify_err();
if result.is_ok() {
handle::Handle::refresh_clash();
@@ -98,6 +97,7 @@ pub async fn stop_core() -> CmdResult {
/// 重启核心
#[tauri::command]
pub async fn restart_core() -> CmdResult {
logging_error!(Type::Core, Config::profiles().await.latest_arc().save_file().await);
let result = CoreManager::global().restart_core().await.stringify_err();
if result.is_ok() {
handle::Handle::refresh_clash();
@@ -126,9 +126,7 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
use tokio::fs;
// 获取DNS配置文件路径
let dns_path = dirs::app_home_dir()
.stringify_err()?
.join(constants::files::DNS_CONFIG);
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
// 保存DNS配置到文件
let yaml_str = serde_yaml_ng::to_string(&dns_config).stringify_err()?;
@@ -141,17 +139,9 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
/// 应用或撤销DNS配置
#[tauri::command]
pub async fn apply_dns_config(apply: bool) -> CmdResult {
use crate::{
config::Config,
core::{CoreManager, handle},
utils::dirs,
};
if apply {
// 读取DNS配置文件
let dns_path = dirs::app_home_dir()
.stringify_err()?
.join(constants::files::DNS_CONFIG);
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
if !dns_path.exists() {
logging!(warn, Type::Config, "DNS config file not found");
@@ -163,10 +153,9 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
})?;
// 解析DNS配置
let patch_config = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml)
.stringify_err_log(|e| {
logging!(error, Type::Config, "Failed to parse DNS config: {e}");
})?;
let patch_config = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml).stringify_err_log(|e| {
logging!(error, Type::Config, "Failed to parse DNS config: {e}");
})?;
logging!(info, Type::Config, "Applying DNS config from file");
@@ -175,7 +164,9 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
patch.insert("dns".into(), patch_config.into());
// 应用DNS配置到运行时配置
Config::runtime().await.draft_mut().patch_config(patch);
Config::runtime().await.edit_draft(|d| {
d.patch_config(&patch);
});
// 重新生成配置
Config::generate().await.stringify_err_log(|err| {
@@ -184,41 +175,30 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
})?;
// 应用新配置
CoreManager::global()
.update_config()
.await
.stringify_err_log(|err| {
let err = format!("Failed to apply config with DNS: {err}");
logging!(error, Type::Config, "{err}");
})?;
CoreManager::global().update_config().await.stringify_err_log(|err| {
let err = format!("Failed to apply config with DNS: {err}");
logging!(error, Type::Config, "{err}");
})?;
logging!(info, Type::Config, "DNS config successfully applied");
handle::Handle::refresh_clash();
} else {
// 当关闭DNS设置时,重新生成配置(不加载DNS配置文件)
logging!(
info,
Type::Config,
"DNS settings disabled, regenerating config"
);
logging!(info, Type::Config, "DNS settings disabled, regenerating config");
Config::generate().await.stringify_err_log(|err| {
let err = format!("Failed to regenerate config: {err}");
logging!(error, Type::Config, "{err}");
})?;
CoreManager::global()
.update_config()
.await
.stringify_err_log(|err| {
let err = format!("Failed to apply regenerated config: {err}");
logging!(error, Type::Config, "{err}");
})?;
CoreManager::global().update_config().await.stringify_err_log(|err| {
let err = format!("Failed to apply regenerated config: {err}");
logging!(error, Type::Config, "{err}");
})?;
logging!(info, Type::Config, "Config regenerated successfully");
handle::Handle::refresh_clash();
}
handle::Handle::refresh_clash();
Ok(())
}
@@ -227,9 +207,7 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
pub fn check_dns_config_exists() -> CmdResult<bool> {
use crate::utils::dirs;
let dns_path = dirs::app_home_dir()
.stringify_err()?
.join(constants::files::DNS_CONFIG);
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
Ok(dns_path.exists())
}
@@ -240,9 +218,7 @@ pub async fn get_dns_config_content() -> CmdResult<String> {
use crate::utils::dirs;
use tokio::fs;
let dns_path = dirs::app_home_dir()
.stringify_err()?
.join(constants::files::DNS_CONFIG);
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
if !fs::try_exists(&dns_path).await.stringify_err()? {
return Err("DNS config file not found".into());
@@ -270,9 +246,6 @@ pub async fn validate_dns_config() -> CmdResult<(bool, String)> {
#[tauri::command]
pub async fn get_clash_logs() -> CmdResult<Vec<CompactString>> {
let logs = CoreManager::global()
.get_clash_logs()
.await
.unwrap_or_default();
let logs = CoreManager::global().get_clash_logs().await.unwrap_or_default();
Ok(logs)
}
@@ -1,10 +1,9 @@
use std::sync::Arc;
use clash_verge_logging::{Type, logging};
use regex::Regex;
use reqwest::{Client, cookie::Jar};
use crate::{logging, utils::logging::Type};
use super::UnlockItem;
use super::utils::{country_code_to_emoji, get_local_date_string};
@@ -12,9 +11,13 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem {
let cookie_store = Arc::new(Jar::default());
let client_with_cookies = match Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
.use_rustls_tls()
.user_agent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
)
.cookie_provider(Arc::clone(&cookie_store))
.build() {
.build()
{
Ok(client) => client,
Err(e) => {
logging!(
@@ -59,8 +62,7 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem {
};
}
let url =
format!("https://ani.gamer.com.tw/ajax/token.php?adID=89422&sn=37783&device={device_id}");
let url = format!("https://ani.gamer.com.tw/ajax/token.php?adID=89422&sn=37783&device={device_id}");
let token_result = match client_with_cookies.get(&url).send().await {
Ok(response) => match response.text().await {
@@ -85,21 +87,14 @@ pub(super) async fn check_bahamut_anime(client: &Client) -> UnlockItem {
};
}
let region = match client_with_cookies
.get("https://ani.gamer.com.tw/")
.send()
.await
{
let region = match client_with_cookies.get("https://ani.gamer.com.tw/").send().await {
Ok(response) => match response.text().await {
Ok(body) => match Regex::new(r#"data-geo="([^"]+)"#) {
Ok(region_re) => region_re
.captures(&body)
.and_then(|caps| caps.get(1))
.map(|m| {
let country_code = m.as_str();
let emoji = country_code_to_emoji(country_code);
format!("{emoji}{country_code}")
}),
Ok(region_re) => region_re.captures(&body).and_then(|caps| caps.get(1)).map(|m| {
let country_code = m.as_str();
let emoji = country_code_to_emoji(country_code);
format!("{emoji}{country_code}")
}),
Err(e) => {
logging!(
error,
@@ -1,7 +1,7 @@
use regex::Regex;
use reqwest::Client;
use crate::{logging, utils::logging::Type};
use clash_verge_logging::{Type, logging};
use super::UnlockItem;
use super::utils::{country_code_to_emoji, get_local_date_string};
@@ -9,8 +9,7 @@ use super::utils::{country_code_to_emoji, get_local_date_string};
#[allow(clippy::cognitive_complexity)]
pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
let device_api_url = "https://disney.api.edge.bamgrid.com/devices";
let auth_header =
"Bearer ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84";
let auth_header = "Bearer ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84";
let device_req_body = serde_json::json!({
"deviceFamily": "browser",
@@ -39,12 +38,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
let device_response = match device_result {
Ok(response) => response,
Err(e) => {
logging!(
error,
Type::Network,
"Failed to get Disney+ device response: {}",
e
);
logging!(error, Type::Network, "Failed to get Disney+ device response: {}", e);
return UnlockItem {
name: "Disney+".to_string(),
status: "Failed (Network Connection)".to_string(),
@@ -120,18 +114,12 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
}
};
let token_body = [
(
"grant_type",
"urn:ietf:params:oauth:grant-type:token-exchange",
),
("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange"),
("latitude", "0"),
("longitude", "0"),
("platform", "browser"),
("subject_token", assertion_str.as_str()),
(
"subject_token_type",
"urn:bamtech:params:oauth:token-type:device",
),
("subject_token_type", "urn:bamtech:params:oauth:token-type:device"),
];
let token_result = client
@@ -154,12 +142,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
let token_response = match token_result {
Ok(response) => response,
Err(e) => {
logging!(
error,
Type::Network,
"Failed to get Disney+ token response: {}",
e
);
logging!(error, Type::Network, "Failed to get Disney+ token response: {}", e);
return UnlockItem {
name: "Disney+".to_string(),
status: "Failed (Network Connection)".to_string(),
@@ -264,12 +247,7 @@ pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
let graphql_response = match graphql_result {
Ok(response) => response,
Err(e) => {
logging!(
error,
Type::Network,
"Failed to get Disney+ GraphQL response: {}",
e
);
logging!(error, Type::Network, "Failed to get Disney+ GraphQL response: {}", e);
return UnlockItem {
name: "Disney+".to_string(),
status: "Failed (Network Connection)".to_string(),
@@ -1,7 +1,7 @@
use regex::Regex;
use reqwest::Client;
use crate::{logging, utils::logging::Type};
use clash_verge_logging::{Type, logging};
use super::UnlockItem;
use super::utils::{country_code_to_emoji, get_local_date_string};
@@ -18,12 +18,7 @@ pub(super) async fn check_gemini(client: &Client) -> UnlockItem {
let re = match Regex::new(r#",2,1,200,"([A-Z]{3})""#) {
Ok(re) => re,
Err(e) => {
logging!(
error,
Type::Network,
"Failed to compile Gemini regex: {}",
e
);
logging!(error, Type::Network, "Failed to compile Gemini regex: {}", e);
return UnlockItem {
name: "Gemini".to_string(),
status: "Failed".to_string(),
@@ -4,7 +4,7 @@ use reqwest::Client;
use tauri::command;
use tokio::{sync::Mutex, task::JoinSet};
use crate::{logging, utils::logging::Type};
use clash_verge_logging::{Type, logging};
mod bahamut;
mod bilibili;
@@ -42,6 +42,7 @@ pub async fn get_unlock_items() -> Result<Vec<UnlockItem>, String> {
#[command]
pub async fn check_media_unlock() -> Result<Vec<UnlockItem>, String> {
let client = match Client::builder()
.use_rustls_tls()
.user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
.timeout(std::time::Duration::from_secs(30))
.danger_accept_invalid_certs(true)
@@ -1,7 +1,7 @@
use reqwest::Client;
use serde_json::Value;
use crate::{logging, utils::logging::Type};
use clash_verge_logging::{Type, logging};
use super::UnlockItem;
use super::utils::{country_code_to_emoji, get_local_date_string};
@@ -50,12 +50,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem {
let status1 = match result1 {
Ok(response) => response.status().as_u16(),
Err(e) => {
logging!(
error,
Type::Network,
"Failed to get Netflix response 1: {}",
e
);
logging!(error, Type::Network, "Failed to get Netflix response 1: {}", e);
return UnlockItem {
name: "Netflix".to_string(),
status: "Failed".to_string(),
@@ -68,12 +63,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem {
let status2 = match result2 {
Ok(response) => response.status().as_u16(),
Err(e) => {
logging!(
error,
Type::Network,
"Failed to get Netflix response 2: {}",
e
);
logging!(error, Type::Network, "Failed to get Netflix response 2: {}", e);
return UnlockItem {
name: "Netflix".to_string(),
status: "Failed".to_string(),
@@ -157,12 +147,7 @@ pub(super) async fn check_netflix(client: &Client) -> UnlockItem {
async fn check_netflix_cdn(client: &Client) -> UnlockItem {
let url = "https://api.fast.com/netflix/speedtest/v2?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=5";
match client
.get(url)
.timeout(std::time::Duration::from_secs(30))
.send()
.await
{
match client.get(url).timeout(std::time::Duration::from_secs(30)).send().await {
Ok(response) => {
if response.status().as_u16() == 403 {
return UnlockItem {
@@ -1,7 +1,7 @@
use regex::Regex;
use reqwest::Client;
use crate::{logging, utils::logging::Type};
use clash_verge_logging::{Type, logging};
use super::UnlockItem;
use super::utils::{country_code_to_emoji, get_local_date_string};
@@ -23,12 +23,7 @@ pub(super) async fn check_prime_video(client: &Client) -> UnlockItem {
let response = match result {
Ok(response) => response,
Err(e) => {
logging!(
error,
Type::Network,
"Failed to get Prime Video response: {}",
e
);
logging!(error, Type::Network, "Failed to get Prime Video response: {}", e);
return UnlockItem {
name: "Prime Video".to_string(),
status: "Failed (Network Connection)".to_string(),
@@ -26,7 +26,7 @@ const DEFAULT_UNLOCK_ITEM_NAMES: [&str; 13] = [
"ChatGPT Web",
"Claude",
"Gemini",
"Youtube Premium",
"YouTube Premium",
"Bahamut Anime",
"Netflix",
"Disney+",
@@ -1,4 +1,5 @@
use chrono::Local;
use rust_iso3166;
pub fn get_local_date_string() -> String {
let now = Local::now();
@@ -6,16 +7,70 @@ pub fn get_local_date_string() -> String {
}
pub fn country_code_to_emoji(country_code: &str) -> String {
let country_code = country_code.to_uppercase();
if country_code.len() < 2 {
return String::new();
}
let uc = country_code.to_ascii_uppercase();
let bytes = country_code.as_bytes();
// 长度校验:仅允许 2 或 3
match uc.len() {
2 => {
// 校验是否是合法 alpha2
if rust_iso3166::from_alpha2(&uc).is_none() {
return String::new();
}
alpha2_to_emoji(&uc)
}
3 => {
// 转换并校验 alpha3
match rust_iso3166::from_alpha3(&uc) {
Some(c) => {
let alpha2 = c.alpha2.to_ascii_uppercase();
alpha2_to_emoji(&alpha2)
}
None => String::new(),
}
}
_ => String::new(),
}
}
fn alpha2_to_emoji(alpha2: &str) -> String {
let bytes = alpha2.as_bytes();
let c1 = 0x1F1E6 + (bytes[0] as u32) - ('A' as u32);
let c2 = 0x1F1E6 + (bytes[1] as u32) - ('A' as u32);
char::from_u32(c1)
.and_then(|c1| char::from_u32(c2).map(|c2| format!("{c1}{c2}")))
.and_then(|x| char::from_u32(c2).map(|y| format!("{x}{y}")))
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::country_code_to_emoji;
#[test]
fn country_code_to_emoji_iso2() {
assert_eq!(country_code_to_emoji("CN"), "🇨🇳");
assert_eq!(country_code_to_emoji("us"), "🇺🇸");
}
#[test]
fn country_code_to_emoji_iso3() {
assert_eq!(country_code_to_emoji("CHN"), "🇨🇳");
assert_eq!(country_code_to_emoji("USA"), "🇺🇸");
}
#[test]
fn country_code_to_emoji_invalid() {
assert_eq!(country_code_to_emoji("XXX"), "");
assert_eq!(country_code_to_emoji("ZZ"), "");
}
#[test]
fn country_code_to_emoji_short() {
assert_eq!(country_code_to_emoji("C"), "");
assert_eq!(country_code_to_emoji(""), "");
}
#[test]
fn country_code_to_emoji_long() {
assert_eq!(country_code_to_emoji("CNAAA"), "");
}
}

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