Compare commits

..
930 Commits
Author SHA1 Message Date
Tunglies f6b9ccc774 ```
nothing changes
```
2025-03-11 01:15:37 +08:00
Tunglies d001bbc2eb ```
No changes made
```
2025-03-11 01:12:20 +08:00
Tunglies 2d1807cd4d feat: enhance tag retrieval and detection for stable and pre-release versions 2025-03-11 01:09:25 +08:00
Tunglies c6dce2d6cb feat: increase tag retrieval limit and refine alpha tag identification 2025-03-11 00:56:35 +08:00
Tunglies 3c7768b379 feat: add support for alpha updates and enhance updater functionality 2025-03-11 00:24:31 +08:00
Tunglies d470bf2dd9 rm: label issues workflow 2025-03-10 01:01:12 +08:00
Tunglies b5e8a1ddb5 chore: rename "DNS Settings" to "DNS Overwrite" in UI for consistency
This change updates the label for the DNS toggle setting from "DNS Settings" to "DNS Overwrite"
in the ClashVerge settings interface. The change provides better consistency with the translation
keys and more clearly communicates the function of the setting, which is to override system DNS.
The corresponding translation keys have been updated in both English and Chinese localization files.
2025-03-10 00:57:55 +08:00
Tunglies 08f3357d9a feat: add scheduled workflow and commit change check to alpha build 2025-03-09 17:42:13 +08:00
Tunglies 107c54921d refactor: update MihomoManager to handle traffic WebSocket URL and authorization 2025-03-09 14:44:15 +08:00
wonfen ca1f7a2b31 feat: enhance latency test logging and error handling 2025-03-09 04:22:34 +08:00
wonfen 95e8fe38f2 refactor: improve webSocket connection handling and error recovery 2025-03-09 04:22:01 +08:00
wonfen cd6eb26a41 feat: add icon file content check to prevent saving failed downloads 2025-03-09 02:34:57 +08:00
Tunglies 9f2c4b4f35 refactor: migrate clash client info retrieval to MihomoManager 2025-03-09 00:40:16 +08:00
Tunglies adbf98cb15 rm: clash-api dead code 2025-03-09 00:29:14 +08:00
Tunglies c9b3fec09c refactor: improve request handling and response processing in MihomoManager 2025-03-09 00:27:12 +08:00
Tunglies 364fb16b7b refactor: streamline clash delay test and improve API interactions 2025-03-09 00:04:48 +08:00
Tunglies 634ed9cb46 refactor: streamline clash mode handling and improve API interactions 2025-03-08 22:41:14 +08:00
wonfen cddb117402 feat: optimize icon cache download and DNS view styling 2025-03-08 13:31:20 +08:00
wonfen bd6c4e00c4 refactor: restructure DNS setting logic 2025-03-08 11:25:00 +08:00
wonfen bbcfb4901f refactor: refine DNS handling to follow config and merge settings 2025-03-08 03:34:25 +08:00
wonfen 230a0eeb64 fix: regex for first character matching 2025-03-07 14:07:10 +08:00
wonfen c4c49f61df perf: faster app exit 2025-03-07 13:44:07 +08:00
TianHua Liu 6d5c4f86d3 fix: Notice @ts-ignore (#2896)
thx
2025-03-07 12:46:30 +08:00
0XE 59f22b9cd2 feat: Add x-data-grid component localization (#2925)
thx
2025-03-07 12:45:56 +08:00
wonfen 6caf2cd057 test: remove entitlements.plist items 2025-03-07 06:49:24 +08:00
Tunglies 684128ca68 Update dependencies and refactor encryption logic
Updates multiple dependencies to their latest versions in Cargo.lock and Cargo.toml.
Refactors encryption logic to use updated getrandom API.
Improves tray speed rate display by using ab_glyph for font rendering.
2025-03-06 18:56:31 +08:00
wonfen 6925adc66f feat: enable dns settings by default 2025-03-06 14:40:35 +08:00
wonfen e1e9d91d86 feat: add dns settings 2025-03-06 14:30:43 +08:00
Christine. 881c136b1e add: missing i18n text. (#2917) 2025-03-05 22:28:28 +08:00
0XE 5bc699b19c fix: ISSUES #2727 (#2913) 2025-03-05 14:20:07 +08:00
Christine. 52ba1c5a30 workflow: remove renaming behavior. (#2909) 2025-03-05 11:41:15 +08:00
Tunglies 817d68546e feat(mihomo): refactor MihomoManager for global access and improve proxy retrieval (#2906) 2025-03-05 10:58:54 +08:00
wonfen c0f5c231ad feat: add admin permission prompt for system service 2025-03-05 10:22:57 +08:00
Tunglies 9677d8670b test: crate_mihomo_api additional headers 2025-03-05 08:14:37 +08:00
Tunglies b01630d31c fix: extern controler api secert with headers 2025-03-05 08:09:42 +08:00
Tunglies f771f4720f refacture: Mihomo API integration (#2900)
* feat: add mihomo_api crate as a workspace member

Added a new mihomo_api crate to handle interactions with the Mihomo API. This modular approach provides a dedicated interface for fetching and managing proxy data from Mihomo servers. The implementation includes functionality to refresh and retrieve both proxies and provider proxies with proper error handling and timeouts. Added this crate as a workspace member and included it as a dependency in the main project.

* Refactors Mihomo API integration

Simplifies proxy fetching by removing the MihomoManager structure.

Updates the get_proxies and get_providers_proxies functions to directly use the mihomo_api module.

Removes unused Mihomo API related files and modules for cleaner codebase.

Enhances overall maintainability and performance.
2025-03-05 00:45:08 +08:00
0XE f0becb5003 fix: issues #2838 (#2886) 2025-03-04 20:46:17 +08:00
Tunglies 405e8df825 feat(sysinfo): Add diagnostic information enhancements (#2880)
Enhanced the PlatformSpecification struct with additional diagnostic information including:
- Added Verge version information to diagnostic output
- Added running mode information (Service/Sidecar/Not Running)
- Improved Debug implementation to display all diagnostic fields
- Implemented asynchronous detection of core running mode

This change helps users provide more complete system information when reporting issues.
2025-03-04 11:52:22 +08:00
wonfen 49fc7ff7ca fix: sync system proxy status indicator with hotkey 2025-03-04 06:57:42 +08:00
wonfen fde9c8aaee fix: correct type declarations for getProxiesInner and getProxyProviders 2025-03-04 02:26:26 +08:00
Tunglies 8f95c28050 feat(proxy): add proxy commands and integrate with API
Add new proxy.rs module with get_proxies and get_providers_proxies commands.
Update mod.rs and lib.rs to re-export and register proxy commands.
Update API.ts to use invoke for proxy commands.
Minor formatting improvements in module/mihomo.rs.
2025-03-04 01:01:24 +08:00
Tunglies 2cd97c7785 feat: add Mihomo API modules and manager (#2869)
• Introduce new API caller implementations for Mihomo in model and module layers.
• Add configuration and API integration files under /src-tauri/src/config/api and /src-tauri/src/model/api.
• Implement a singleton MihomoAPICaller with async API call support and integration tests.
• Create a new MihomoManager module to refresh and fetch proxies from the API.
• Update Cargo.lock and Cargo.toml with additional dependencies (async-trait, env_logger, mockito, tempfile, etc.) related to the Mihomo API support.
2025-03-03 19:31:44 +08:00
wonfen 6e48781687 Revert "refactor: improve proxy group UI and spacing (#2835)"
This reverts commit b31cbda615.
2025-03-03 14:47:05 +08:00
wonfen dd510b2ee9 feat: show service mode installation prompts in user mode 2025-03-03 14:42:31 +08:00
Tunglies fadae3f7dc fix: speed format runns by docs 2025-03-03 11:36:21 +08:00
Tunglies fdf8a6b2ba Revert "style: update box styling in settings page for improved layout (#2857)"
This reverts commit 4382df7951.
2025-03-03 08:00:10 +08:00
Tunglies 4382df7951 style: update box styling in settings page for improved layout (#2857) 2025-03-03 06:38:32 +08:00
Tunglies 6cb7d48530 feat: add export diagnostic info functionality (#2856) 2025-03-03 05:58:12 +08:00
wonfen 9d74b93ee0 feat: Add sidecar mode as an alternative to service mode
- Auto-fallback to sidecar mode if service mode fails
2025-03-03 03:34:34 +08:00
Tunglies d370868222 fix: windows/linux runtime crash 2025-03-03 02:27:45 +08:00
Christine. 9278f9e193 workflow: remove 32-bit platform (#2855)
* chore: build portable by self

* chore: remove 32bit platform

* Update CONTRIBUTING.md

* update alpha version
2025-03-03 01:16:33 +08:00
Tunglies a708db6f2b Revert "chore: alpha ci should remove old builds"
This reverts commit d2c4a44297.
2025-03-03 00:16:17 +08:00
Tunglies d2c4a44297 chore: alpha ci should remove old builds 2025-03-02 23:46:20 +08:00
Tunglies 3a24623e4b feat: fish env export support 2025-03-02 23:20:10 +08:00
Tunglies d2084d8d21 version: 2.1.3 a 2025-03-02 20:46:53 +08:00
Tunglies d9b4ca91e7 version: 2.1.3 alpha 2025-03-02 20:43:54 +08:00
Tunglies 13da571ad2 version: 2.1.3 alpha (#2851)
* version: 2.1.3 alpha
2025-03-02 19:08:27 +08:00
Tunglies 662d60c0f5 chore: automatically label issues (#2844) 2025-03-02 18:50:31 +08:00
Tunglies 368095d2d4 docs: add fast build and clean commands to contributing guide (#2842)
docs: add fast build and clean commands to contributing guide

- Added documentation for the `pnpm build:fast` command which uses Rust's fast-release profile to reduce compilation time
- Added explanation that fast builds disable optimization and LTO, resulting in larger but quicker builds
- Added documentation for the `pnpm clean` command to clean Rust build files
2025-03-02 15:13:17 +08:00
Tunglies 496aeeb06d chore: fast-dev and fast-build profile (#2841)
* refactor: improve proxy group UI and spacing

- Increased spacing in proxy-groups.tsx by adjusting the right position
  of the alphabet selector to provide better visual separation
- Enhanced spacing in proxy-render.tsx with larger margins and padding
  - Increased group item margins from 8px to 10px with 16px horizontal spacing
  - Expanded border radius from 8px to 10px for smoother appearance
  - Improved ProxyHead component spacing with pl: 3, pr: 3.5
  - Enhanced grid spacing in proxy collection items from 1 to 1.5
  - Adjusted padding for better visual hierarchy

These changes create a more polished, spacious layout with improved
readability and touch targets.

* - Update package.json with improved dev and build scripts:
  - Add fast-dev profile to development scripts
  - Configure build:fast with fast-release profile
  - Add clean command for cargo cleaning
2025-03-02 14:58:59 +08:00
Tunglies b31cbda615 refactor: improve proxy group UI and spacing (#2835)
- Increased spacing in proxy-groups.tsx by adjusting the right position
  of the alphabet selector to provide better visual separation
- Enhanced spacing in proxy-render.tsx with larger margins and padding
  - Increased group item margins from 8px to 10px with 16px horizontal spacing
  - Expanded border radius from 8px to 10px for smoother appearance
  - Improved ProxyHead component spacing with pl: 3, pr: 3.5
  - Enhanced grid spacing in proxy collection items from 1 to 1.5
  - Adjusted padding for better visual hierarchy

These changes create a more polished, spacious layout with improved
readability and touch targets.
2025-03-02 05:36:18 +08:00
Tunglies 7898f3a119 fix: macos frameless title 2025-03-02 04:30:58 +08:00
wonfen ba0a291d97 feat: refactor logging system into a global service 2025-03-02 04:20:38 +08:00
wonfen b3e4defc0f fix: remove macos window title 2025-03-02 04:09:50 +08:00
wonfen 1ae0ad8a0e fix: add bottom padding to prevent jitter 2025-03-02 04:08:13 +08:00
Tunglies 79b20694c7 feat: add fast compilation options for development and release (#2831)
- Added fast compilation profiles in Cargo.toml
  - fast-dev profile with max codegen units and disabled optimizations
  - fast-release profile with debugging support and faster build time
- Added new npm scripts for quick development iterations
  - dev:fast command for standard development without extra features
  - build:fast command for quick release builds
- Updated default dev command to use verge-dev feature flag
- Both profiles retain debug symbols and disable stripping for better debugging
2025-03-02 00:40:07 +08:00
Tunglies 1ad4941ed8 refactor: rename cmds module to cmd for better consistency (#2830)
- Renamed `cmds` module to `cmd` for better naming consistency
- Reorganized command modules into separate files under src/cmd/
- Updated all imports and references to use the new module name
- Fixed missing dependency in webdav.rs to reference core::backup
- Updated tray module to use new cmd namespace
- Improved uwp.rs module structure using platform-specific implementations
- Removed unnecessary imports from various command files
2025-03-01 22:52:43 +08:00
Tunglies b3fd44d165 refactor: reorganize feat.rs into modular structure (#2827)
Split the monolithic feat.rs file into specialized modules:
- backup.rs: WebDAV backup and restore functions
- clash.rs: Core management and testing functions
- config.rs: Configuration handling
- profile.rs: Profile management
- proxy.rs: Proxy and TUN mode controls
- window.rs: Dashboard window management

This improves code organization, readability, and maintainability
by grouping related functionality into logical modules.
2025-03-01 20:44:35 +08:00
Christine. 391a494af6 fix: build failed with Windows (#2825) 2025-03-01 19:52:42 +08:00
Tunglies 623d075ab8 add: issues type template 2025-03-01 19:12:12 +08:00
Tunglies 5d70b77316 Issues label options template (#2820)
* fix: macos dock display icon and text

* add: issues label option template
2025-03-01 02:46:16 -08:00
Tunglies f5f54c0f0e fix: macos dock display icon and text (#2818) 2025-03-01 02:29:40 -08:00
wonfen 91b0b1f279 perf: improve scrolling performance and interaction in proxy group list 2025-03-01 08:31:31 +08:00
wonfen e0fa1f3efe fix: v2 action file rename 2025-03-01 08:04:31 +08:00
wonfen b00abf3337 chore: updatelog 2025-03-01 03:45:03 +08:00
wonfen a5d846ce4e feat: add exit status check in core config validation 2025-03-01 03:39:13 +08:00
wonfen 7743c42dd1 Release - 2.1.2 真·臻 2025-03-01 01:49:01 +08:00
Christine. 3f3dab9495 add: i18n text for settings page. (#2815) 2025-03-01 01:36:36 +08:00
Tunglies 2dc9672b20 feat: better setting UI layout (#2814) 2025-03-01 01:29:23 +08:00
wonfen 2c90d1ca69 chore: v2 updater 2025-03-01 01:29:23 +08:00
Tunglies 1555e2910d feat: better setting layout ui (#2809) 2025-03-01 01:29:23 +08:00
Tunglies 19b6bd35f5 feat: quiting when enable tun mode no more blocking system network (#2805) 2025-03-01 01:29:23 +08:00
wonfen 3d4d60edc8 test updater 2025-03-01 01:29:23 +08:00
wonfen db0a000ecb chore: updater v2 Capabilities 2025-02-28 10:55:53 +08:00
wonfen 24ff48b41c fix: updater 2025-02-28 09:33:50 +08:00
wonfen 18a67d0b2f chore: updatelog 2025-02-28 07:34:56 +08:00
wonfen e23af1ad58 feat: add ability to check service version and auto-reinstall 2025-02-28 06:45:30 +08:00
wonfen 70af392059 feat: enhance updater script with comprehensive platform support and logging 2025-02-27 17:19:04 +08:00
Christine. d255df23ee fix: connection details (#2778) 2025-02-27 14:53:17 +08:00
wonfen 1317a8b2db feat: enhance merge config validation and error handling 2025-02-27 14:49:55 +08:00
wonfen 4cac118442 style: UI tweak 2025-02-27 13:04:46 +08:00
wonfen 2ce43ccd23 Release - 2.1.1 臻fix 2025-02-27 03:18:23 +08:00
Tunglies d23b2949d8 feat: macos display colorful icon with speed rate (#2771) 2025-02-27 01:51:52 +08:00
wonfen 3471476ba2 feat: improve file type detection for better script recognition 2025-02-26 15:59:19 +08:00
wonfen cee61e5619 chore: downgrade Tauri updater plugin and add i18n for core switching 2025-02-26 15:04:47 +08:00
wonfen 5026e2bade feat: optimize hotkey behavior and window management logic 2025-02-26 11:03:50 +08:00
wonfen 83f005c256 fix: CI 2025-02-26 09:52:15 +08:00
wonfen 80843c5ee3 Release 2.1.0 - 臻 2025-02-26 08:36:02 +08:00
wonfen ee00defe43 feat: enhance script validation and error handling 2025-02-26 05:21:14 +08:00
wonfen 7696504d97 feat: Add window state monitoring and auto-save in real-time 2025-02-26 00:36:02 +08:00
wonfen 7a0e38a1b4 perf: Improve config validation error messages and handling 2025-02-25 13:47:29 +08:00
wonfen bc5d577553 chore: revise translation 2025-02-24 23:26:04 +08:00
wonfen cfed2b7236 chore: Change default TUN stack from 'mixed' to 'gvisor' 2025-02-24 23:10:09 +08:00
Christine. e34e6654bc add: i18n text for config check (#2750) 2025-02-24 22:23:42 +08:00
wonfen d6b85f1a01 fix: remove node related group info when deleting a node 2025-02-24 11:40:28 +08:00
wonfen 16af7b53cb fix: menu switching issue 2025-02-24 11:37:23 +08:00
wonfen befc856207 feat: Improve core change configuration validation and error handling 2025-02-24 07:34:03 +08:00
wonfen 5ab8e7a7c7 feat: Enhance configuration validation and error handling during app startup 2025-02-24 06:21:32 +08:00
zhaoyuanandzymouse 443bfa5928 feat: 通过添加CLASH_VERGE_REV_IP环境变量的方式,修改复制环境变量按钮的IP (#2734)
Co-authored-by: zymouse <zymouse@pixmoving.net>
2025-02-24 03:42:40 +08:00
wonfen 52627575ff feat: Enhance configuration validation and error handling
- Improve config validation process with detailed logging and error tracking
- Add more robust error handling in profile updates and config patches
- Implement comprehensive config validation using clash core subprocess
2025-02-23 10:53:09 +08:00
Christine. 39973f2d24 feat: auto rename alpha version (#2740) 2025-02-23 02:30:15 +08:00
wonfen 2b01857d15 chore: Disable automatic alpha tag update in workflow 2025-02-20 14:25:58 +08:00
wonfen 2650aa845f refactor: Optimize proxy rendering and layout calculation 2025-02-20 14:21:55 +08:00
wonfen 8400a61bf0 refactor: Simplify tray icon event handling across platforms 2025-02-20 07:03:28 +08:00
wonfen 763b6b9003 chore: Update Node.js version and remove updater file generation 2025-02-20 02:55:47 +08:00
wonfen 053f414f3c feat: Add system proxy status indicators 2025-02-19 13:56:22 +08:00
wonfen 2defa2cc56 refactor: Simplify log data management and improve search functionality 2025-02-19 13:06:15 +08:00
wonfen bb1dbfcfc3 fix: connections page traffic calculation 2025-02-19 02:24:21 +08:00
Tunglies 1378068a30 rm dead code (#2718)
* rm: verge service takes full control of mihomo process. no more required.

* rm: dead code
2025-02-19 01:13:52 +08:00
wonfen 5d9dce7d10 style: tweak 2025-02-18 11:40:41 +08:00
wonfen acb66a3012 feat: Add persistent column width settings for connection table & process filtering 2025-02-18 11:04:22 +08:00
wonfen 054f902cc6 refactor: Remove unused routing select component from settings page 2025-02-18 09:04:23 +08:00
wonfen 4333153a59 feat: Improve letter item hover interaction with debounce mechanism 2025-02-18 09:00:03 +08:00
wonfen 19e623d7c2 feat: Add pause/resume functionality for connection tracking 2025-02-18 08:14:09 +08:00
wonfen 2dd646537e fix: resolve deprecated warnings in console 2025-02-18 07:10:28 +08:00
wonfen 1ece079978 perf: Improve proxy list interaction and UI responsiveness 2025-02-18 05:54:16 +08:00
wonfen 0d8f779634 feat: Enhance alphabet selector with dynamic tooltip and scrolling 2025-02-18 01:55:44 +08:00
wonfen a7ffc9fc38 refactor: Improve proxy data update mechanism with optimistic UI and error handling 2025-02-18 00:37:55 +08:00
wonfen 932b29e66f fix: Refine TypeScript types for proxy groups component 2025-02-17 16:27:06 +08:00
wonfen 6be7a3b94c feat: Enhance proxy groups with Initials navigation and performance optimizations 2025-02-17 16:07:46 +08:00
wonfen 8e8dd1ec03 feat: Optimize tray speed rate rendering and update logic 2025-02-17 15:08:19 +08:00
wonfen 772f3268ce feat: Improve Virtuoso list rendering for proxy groups 2025-02-17 14:30:21 +08:00
wonfen a179591ac2 feat: Add persistent scroll position for proxy groups 2025-02-17 14:24:33 +08:00
wonfen d071e5971f fix: alpha build win webview pack rename issue 2025-02-17 12:06:57 +08:00
wonfen a9032f5f20 chore: Update updater artifacts configuration 2025-02-17 11:22:14 +08:00
Akioe Yu 0190702616 fix: check script error (#2453)
(cherry picked from commit 97bde64fd6516019a190d52f05af54ade623d57c)
2025-02-17 03:10:19 +08:00
wonfen c35bf38420 chore: Add macOS-specific test for format_bytes_speed function 2025-02-17 03:10:03 +08:00
wonfen 89d20e564a Revert "perf: Improve kernel management logic & add more dev mode logs"
This reverts commit 18c7ed1ccc.
2025-02-15 05:51:46 +08:00
Christine. 3c24ff3afc add: i18n text for lightweight mode (#2692) 2025-02-13 14:27:12 +08:00
Tunglies daf0398750 fix: build front cannot find IvergeConfig.enable_lite_mode and macos port switching causes crash (#2691)
* fix: macos switch protocol port or enable protocol port causes crash

* fix: build time front cannot find IVergeConfig attribute enable_lite_mode
2025-02-12 18:14:13 -08:00
wonfen 6b14c2b763 feat: add lightweight mode 2025-02-13 02:18:17 +08:00
wonfen 5bf2f9b8ed fix(macos): add missing required dependencies 2025-02-12 15:06:42 +08:00
wonfen 10a2655288 fix: remove unused imports to resolve compile warnings 2025-02-12 14:35:49 +08:00
wonfen ac07397818 refactor: improve hotkey management, logging, and error handling; fix tray freeze and hotkey failure on silent startup 2025-02-12 14:23:42 +08:00
wonfen 30d061d00f fix(tray): resolve blank icon issue on Windows and optimize creation logic 2025-02-12 13:56:33 +08:00
Christine. 360d8a5143 fix: use remoteDestination replace DestinationIP in connection page, #2668 (#2679)
* fix: use remoteDestination replace DestinationIP  in connection page, #2668

* add: missing i18n text

* fix: display the target address details in connection page
2025-02-11 14:52:27 +08:00
wonfen 70e0a5adc8 refactor: Improve tray icon and event handling across platforms & unify click behavior 2025-02-11 14:48:31 +08:00
wonfen ef52f81494 fix: app crash without a default global_hotkey vaule 2025-02-11 01:50:48 +08:00
wonfen e5c1de3ad3 fix: deprecated CLI warnings 2025-02-11 01:05:51 +08:00
wonfen 576fd700ae feat: Add independent alpha update channel 2025-02-11 00:53:44 +08:00
wonfen 476df65a1b fix: try to fix linux CI updater problem 2025-02-11 00:53:44 +08:00
Christine. bd0a4863a8 add: i18n text (#2675) 2025-02-10 00:29:43 -08:00
wonfen d25fbc05e2 perf: Optimize kernel shutdown speed & logic 2025-02-10 13:18:00 +08:00
wonfen b8a0b6f1f4 fix: crash caused by global_hotkey 2025-02-10 12:39:07 +08:00
wonfen 18c7ed1ccc perf: Improve kernel management logic & add more dev mode logs 2025-02-09 14:19:15 +08:00
Tunglies b14db06955 perf: imporve clash mode switching performance on the main window (#2667) 2025-02-09 07:45:46 +08:00
Tunglies 215dcee3f1 feat: option to enable global hotkey (#2665) 2025-02-09 07:45:22 +08:00
Tunglies 37e5c22a5a fix: duplicate checked tray menu when profile name are same (#2660) 2025-02-08 09:36:04 +08:00
wonfen bb0b6c3f77 feat: Optimize UI layout and page hierarchy 2025-02-07 09:13:30 +08:00
wonfen a37df46ce7 perf: optimize node latency refresh rate for faster updates 2025-02-06 07:58:42 +08:00
wonfen 73fbec2514 chore: update deps 2025-02-05 14:19:43 +08:00
Tunglies f66fa08b2c Feature: Switch Proxy Profile from Tray Menu (#2644) 2025-02-05 08:52:47 +08:00
wonfen bae606bc9d fix: restore hotkey functionality after silent startup 2025-02-02 11:37:10 +08:00
wonfen 1a2d5e988a feat: allow users to customize enhanced-mode and fakeip-range in tun mode by wonfen 2025-02-02 05:12:16 +08:00
wonfen d20bd62b90 style: refine tray speed display 2025-02-02 03:43:15 +08:00
wonfen 204bf43b9a fix: windows rounded corners 2025-01-19 09:47:43 +08:00
wonfen 3ea0d20e2c add donation 2025-01-18 16:08:24 +08:00
wonfen 6d527a1cdb release 2.0.3 2025-01-16 03:30:07 +08:00
白铭骢 (Mingcong Bai) 9c1ece754e fix(locales): clean up typesetting (#2539)
Following localisation guidelines from 《大陆简中自由软件本地化工作指南》
1.5.4 版 (Simplified Chinese - Mainland China FOSS Localization
Guidelines, version 1.5.4).

Link: https://repo.aosc.io/aosc-l10n/zh_CN_l10n_1.5.4.pdf
2025-01-14 13:00:30 +08:00
huzibaca 9070260d41 chore: update 2025-01-14 12:56:08 +08:00
huzibaca cd8df52aad revert: update deps 2025-01-14 12:41:52 +08:00
huzibaca fa86efcdfb chore: avoid duplicate updates when tray rate is off 2025-01-14 11:48:43 +08:00
huzibaca 1a61fab79a feat: maoos tray speed can be closed 2025-01-13 20:48:25 +08:00
huzibaca 41a27641df fix: fix: try to fix the language pack issue(2) 2025-01-13 17:09:38 +08:00
huzibaca a39696151d fix: windows tray icon color not updated 2025-01-13 13:49:56 +08:00
huzibaca 4a4ca5c409 chore: update husky/pre-commit 2025-01-13 13:03:46 +08:00
huzibaca 5bff7ea469 fix: tray tooltip not updating 2025-01-13 13:01:12 +08:00
huzibaca f2cc116ff9 chore: update 2025-01-13 12:41:07 +08:00
huzibaca 80a18c9172 chore: update deps 2025-01-12 23:10:15 +08:00
huzibaca a56732e0a3 fix: try to fix the language pack issue 2025-01-12 22:22:06 +08:00
huzibaca 9655f7712b Merge branch 'languagefixes' into dev
# Conflicts:
#	src-tauri/Cargo.lock
#	src-tauri/Cargo.toml
#	src-tauri/src/core/tray/mod.rs
2025-01-11 15:07:30 +08:00
huzibaca 13b63b5d96 fix: fixes #2502 2025-01-11 12:55:20 +08:00
lucidhz c5989d2735 fix: put_configs response add detail error message (#2492)
感谢pr
2025-01-05 01:27:12 -08:00
huzibaca fb4032d6ce fix: modify the external control access key, the tray rate display is abnormal 2025-01-02 15:27:28 +08:00
huzibaca a29c2d4b14 chore: update deps 2025-01-02 15:02:07 +08:00
huzibaca aa7e911c63 chore: update 2025-01-01 08:25:31 +08:00
huzibaca eeff4d41f4 feat: the tray displays the shortcut keys that have been set 2025-01-01 08:14:15 +08:00
huzibaca 086f023ebc chore: update 2024-12-31 11:11:29 +08:00
huzibaca b70336c026 fix: unused code 2024-12-31 08:08:52 +08:00
huzibaca 7d84279370 fix: syntax issues 2024-12-31 04:50:12 +08:00
huzibaca 1b2f1b6106 Merge branch 'feat-macos-spped-rate-icon' into dev 2024-12-31 04:43:54 +08:00
huzibaca fb41c915cc feat: macos system tray addition rate display 2024-12-31 04:42:55 +08:00
blagodaren bab291a60b fix: small refactor 2024-12-28 13:57:30 +03:00
blagodaren 8461046a4f fix: add more lang and lang fix for tray 2024-12-28 10:05:30 +03:00
blagodaren fc925ea032 fix: add system language sup 2024-12-28 08:12:46 +03:00
huzibaca 542baf9d69 fix: fixes #2460 2024-12-28 06:05:09 +08:00
huzibaca 3f02859203 chore:add macos entitlements.list 2024-12-27 04:38:34 +08:00
huzibaca af97bd15a9 chore: update 2024-12-27 02:42:35 +08:00
huzibaca 2c081e5a04 chore: secret is empty and no parameters are passed 2024-12-25 02:15:06 +08:00
huzibaca 2d2521e434 chore: update 2024-12-24 06:03:23 +08:00
huzibaca d9291d4f79 refactor: when updating the tray, the logic is split to improve performance. 2024-12-24 04:52:14 +08:00
huzibaca 0669f7a10b chore: after saving the configuration file, restart the core 2024-12-24 02:22:46 +08:00
huzibaca f0d953ff59 chore: update default_bypass
1. add 172.29.0.0/16
2024-12-23 06:06:46 +08:00
huzibaca 4f6ca40811 fix: turn off window shadow fixer, fixes #2425 2024-12-23 04:36:41 +08:00
huzibaca 47848099be chore: update 2024-12-15 01:14:16 +08:00
huzibaca f839d3bc88 fix: fixer #2346 2024-12-14 17:30:58 +08:00
huzibaca 800f994b10 chore: update deps 2024-12-14 16:21:16 +08:00
Langning Chen b149084e39 Fix l10n issues (#2315)
* Complete the missing translation

* Match the Chinese language file order with others
2024-12-11 01:02:21 +08:00
huzibaca e6580f2f05 chorea: update deps 2024-12-10 22:36:13 +08:00
huzibaca c5c840d378 feat: improve system bypass settings 2024-12-10 14:37:11 +08:00
huzibaca 2e0be4b426 chore: enable default proxy bypass and hide custom bypass settings 2024-12-10 13:59:13 +08:00
huzibaca 460d72f392 chore: update deps(tauri-plugin-autostart) 2024-12-08 22:10:15 +08:00
huzibaca aa18c4870d chore: deeplink uses the latest API 2024-12-08 15:54:46 +08:00
huzibaca 61533646ad chore: update deps 2024-12-08 09:58:59 +08:00
huzibaca b42d13f573 fix: when the window is hidden, close the websocket connection, reduce the risk of memory leaks 2024-12-07 16:47:41 +08:00
wonfen 0e3b631118 chore: change fakeip range 2024-12-06 15:34:41 +08:00
huzibaca 38745d4513 chore:set the sysproxy_rs version to a fixed version 2024-12-06 15:03:04 +08:00
huzibaca d22b37c7bf fix: extension script dns is overwritten fixer #2235 2024-12-06 12:35:09 +08:00
Myles Mo d233a84362 feat: add nullshell env variable (#2285) 2024-12-04 19:01:13 +08:00
huzibaca 589324b582 fix: shift hotkey conversion fixer #2278 2024-12-04 13:19:56 +08:00
huzibaca c11efcb9be chore: update version 2024-12-04 00:30:46 +08:00
huzibaca 6197249377 fix: set fontLigatures to false fixer #2267 2024-12-04 00:28:33 +08:00
lollapalooza c71e18e97e feat: add bypass check feature (#2272) 2024-12-03 16:18:07 +08:00
Christine. f400f900e6 fix: #2126 (#2233) 2024-12-03 16:03:21 +08:00
wonfen ae5b2cfb79 release 2.0.2 2024-12-01 11:00:09 +08:00
huzibaca 0bb8786ef2 fix: resource file initialization failed 2024-12-01 09:38:29 +08:00
huzibaca f7d5be774d fix: resolve service permission failed(2) 2024-11-30 10:45:53 +08:00
huzibaca c0a0b82fa6 chore: update 2024-11-30 10:38:15 +08:00
huzibaca 277ef51375 fix: resolve service permission failed 2024-11-30 10:29:16 +08:00
huzibaca 67b1cf9e1b feat: user uploaded icons can use templates, provided they are monochrome icons fixer #2213 2024-11-30 09:49:43 +08:00
huzibaca 4f797eb7b8 chore: update 2024-11-30 07:04:09 +08:00
huzibaca 29ccabc054 refactor: update_core_config, simplify logic and delete invalid notifications 2024-11-30 05:43:59 +08:00
yyhhyy 857574cbc1 🐛 fix: Dynamically set IPv6 DNS configuration based on existing config (#2198)
thanks
2024-11-30 00:55:15 +08:00
wonfen acc97f28b5 chore: change mac tun dns 2024-11-29 11:10:41 +08:00
huzibaca 02e32aec41 chore: update 2024-11-29 03:46:37 +08:00
huzibaca fe0fdc5603 chore: remove debug code 2024-11-28 14:44:13 +08:00
Yu-Haifeng 66941a18c0 fix: macOS tray icon not changed when use system proxy or tun mode (#2176) 2024-11-28 14:42:05 +08:00
huzibaca f772f92b88 fix: the deadlock caused by incorrect call of window_state due to document error 2024-11-28 12:54:55 +08:00
huzibaca d80db8c91e chore: clean startup registry keys for older versions(2) 2024-11-28 06:40:44 +08:00
huzibaca a1e67820c7 chore: clean startup registry keys for older versions 2024-11-28 05:34:56 +08:00
wonfen ebe0cfbb0c chore: set tun default stack 2024-11-28 04:22:13 +08:00
huzibaca 59c52199e3 chore: update 2024-11-27 14:27:16 +08:00
wonfen 148def4c0f release 2.0.2 2024-11-27 12:41:51 +08:00
huzibaca 3177ea0f4d chore: update 2024-11-27 11:34:52 +08:00
huzibaca 7d65ce847a fix: when tun is closed, the full profile configuration is not restored 2024-11-27 10:35:42 +08:00
huzibaca 2ac27fcfa7 refactor: backup implementation
1. timeouts can be set for different operations
2. performance optimization
2024-11-27 07:34:34 +08:00
huzibaca 373c8ec0e5 chore: update 2024-11-27 07:09:03 +08:00
huzibaca 7df5ca1912 fix: linux build failed 2024-11-27 05:54:48 +08:00
huzibaca d8dc60ff0d fix: fixer #2158 2024-11-27 05:47:08 +08:00
huzibaca 56deafee61 chore: update version 2024-11-27 05:09:46 +08:00
huzibaca 4c29850e1c fix: build failed. #2156 2024-11-27 05:07:02 +08:00
huzibaca 90bf5e0782 chore: timeout adjusted to 15 seconds 2024-11-26 15:09:58 +08:00
huzibaca c2d07ce4c1 fix: add scroll bar and scroll to top button to the test page . fixer #2118 2024-11-26 14:44:43 +08:00
wonfen 564d81fd31 style: adjust 2024-11-26 12:11:20 +08:00
huzibaca f8b7b82ba9 chore: remove window title 2024-11-26 10:11:55 +08:00
huzibaca 71cb2a97c0 fix: kernel-caused silent mode failure to start windows 2024-11-26 09:00:01 +08:00
huzibaca 1cc6472002 chore: update 2024-11-26 05:46:34 +08:00
huzibaca 9b2c80494d refactor: Implement using third-party libraries 2024-11-26 04:51:19 +08:00
huzibaca 4ccc2a2961 chore: update 2024-11-26 03:30:15 +08:00
huzibaca 17a8dfb58a fix: file drag and drop import cannot be used 2024-11-26 03:07:25 +08:00
wonfen 184b588f20 style: Increased light color contrast to prevent blurring on some displays 2024-11-26 01:05:30 +08:00
huzibaca 87c4ebe0da chore: replace mui grid with mui grid2 2024-11-25 12:51:13 +08:00
wonfen 18a0a15e16 update change log 2024-11-25 06:46:29 +08:00
huzibaca c8fd62f388 fix: Try to fix vcruntime duplicate installation 2024-11-25 06:33:47 +08:00
huzibaca 97f03de295 fix: update failed(updater.install called before updater.download) 2024-11-25 05:27:26 +08:00
huzibaca 2c0a5666fc fix: when restoring webdav, the current username, password and url are not preserved 2024-11-25 03:41:35 +08:00
huzibaca 5c3f0d5b60 fix: password failed due to character escaping 2024-11-25 02:48:56 +08:00
huzibaca 746763ed34 fix: remove comments from svg icons to prevent front-end crashes. fixer #2093 2024-11-25 01:58:02 +08:00
wonfen e3800a575f fix: fakeip dns 2024-11-25 01:50:30 +08:00
huzibaca c78c936762 chore: replace mui grid with mui grid2 2024-11-25 01:34:18 +08:00
huzibaca ea5d6f9c46 fix: tun allocates the wrong private network segment, causing conflicts. 2024-11-25 01:06:51 +08:00
wonfen 97f8022276 release 2.0.1 2024-11-24 09:00:20 +08:00
huzibaca d718bd9141 fix: If an older version of the executable exists, delete it(2) 2024-11-24 08:25:46 +08:00
huzibaca 8ddd48eda1 feat: try to use vscode first, if not found then use system default app 2024-11-24 08:20:00 +08:00
huzibaca 3e20404959 fix: If an older version of the executable exists, delete it 2024-11-24 01:24:51 +08:00
huzibaca dde7ead751 Merge branch 'fix-migrate-tauri2-errors'
* fix-migrate-tauri2-errors: (288 commits)

# Conflicts:
#	.github/ISSUE_TEMPLATE/bug_report.yml
2024-11-24 00:14:46 +08:00
huzibaca be719680b0 fix: windows cannot open yaml file 2024-11-23 19:46:53 +08:00
wonfen d6f5a79ac9 fix: release workflow 2024-11-23 12:36:16 +08:00
wonfen a3140f48b4 release 2.0.0 2024-11-23 11:34:17 +08:00
huzibaca 335883f9f0 chore: set the request timeout to 3 seconds 2024-11-23 06:50:28 +08:00
huzibaca f2b1b88242 chore: remove notes 2024-11-23 06:47:43 +08:00
huzibaca 4163ee484c chore: update 2024-11-23 06:42:05 +08:00
huzibaca a14c3f81b0 fix: restore the window state first, then set the window size 2024-11-23 06:39:25 +08:00
huzibaca f08d2c998d chore: update 2024-11-23 06:22:46 +08:00
huzibaca 1b0cf938d9 chore: add debug codes & logs(2) 2024-11-23 06:20:13 +08:00
huzibaca 03d714f2d4 chore: add debug codes & logs 2024-11-23 05:43:13 +08:00
huzibaca 4feef53cb0 chore: set the request timeout to 5 seconds 2024-11-22 14:15:31 +08:00
huzibaca 397e6a9d57 feat: added scroll top button for agent and rule pages 2024-11-22 09:22:44 +08:00
huzibaca 9df2ceba84 chore: remove debug code 2024-11-22 09:08:25 +08:00
huzibaca 1b65dc7256 fix: macOS DNS restore failed 2024-11-22 03:09:39 +08:00
huzibaca 4ffadab4c5 chore: hide DNS cache file 2024-11-22 02:42:55 +08:00
huzibaca 7ed90ff7f8 fix: husky - install command is DEPRECATED 2024-11-21 12:06:52 +08:00
huzibaca ec79126b4a chore: remove unused code 2024-11-21 11:44:19 +08:00
huzibaca ee13bb559e chore: remove unused code 2024-11-21 11:24:19 +08:00
huzibaca ffbc0cdff4 fix: password should not be trimmed 2024-11-21 11:14:40 +08:00
huzibaca 801b79b728 chore: remove unused code 2024-11-21 11:13:11 +08:00
huzibaca cd1d719a92 fix: serde::json passing IVerge to the front end without deserialization 2024-11-21 06:01:56 +08:00
huzibaca cf3848f15d chore: update 2024-11-20 07:37:30 +08:00
huzibaca df5d43283b chore: remove notes 2024-11-20 07:37:03 +08:00
huzibaca 91f0f9f65e feat: encryption configuration properties 2024-11-20 07:27:42 +08:00
huzibaca 708e5d1941 fix: auto launch does not worki 2024-11-20 03:52:19 +08:00
huzibaca 8773117e50 chore: update 2024-11-20 01:15:03 +08:00
huzibaca 8840e63c1c fix: exit_app does not work 2024-11-20 01:04:55 +08:00
huzibaca 4e2d9d6acd fix: windows cannot save window state(2) 2024-11-20 00:27:53 +08:00
huzibaca 94259f9515 fix: windows cannot save window state 2024-11-19 23:32:32 +08:00
huzibaca 5b0ff01cbf chore: update deps 2024-11-19 13:35:57 +08:00
huzibaca 3ae1109a31 chore: format 2024-11-19 13:28:17 +08:00
wonfen f50b9655ae feat: improve set dns logic 2024-11-19 11:38:23 +08:00
huzibaca 081daee53f chore: update web:build comman, use tsc --noEmit 2024-11-19 04:42:37 +08:00
huzibaca 95123aceb5 feat: add logger highlighting, support regular and case matching 2024-11-19 04:10:10 +08:00
huzibaca 2a7b22c96f feat: add logger highlighting 2024-11-19 03:29:44 +08:00
huzibaca c1b15490c1 fix: log pause button not working 2024-11-19 02:56:58 +08:00
huzibaca f8cb84a706 feat: logger support all level filters 2024-11-19 01:40:45 +08:00
huzibaca 063f9034a1 chore: update deps 2024-11-18 23:51:43 +08:00
huzibaca 9c1b2ff89c chore: remove unused code 2024-11-18 23:48:02 +08:00
Christine. 44c05d9fd9 fix: field error, #2044 (#2045) 2024-11-18 07:34:57 -08:00
huzibaca dd3c149e98 refactor: remove useSWRSubscription and use useEffect 2024-11-18 16:41:17 +08:00
huzibaca 0623fe4dc3 chore: remove unused code 2024-11-18 08:16:31 +08:00
huzibaca 2746ff68c8 feat: Log level status is saved to local storage 2024-11-18 08:14:21 +08:00
huzibaca 62eb070c1b refactor: use zustand store, rewrite log clearing logic 2024-11-18 06:48:23 +08:00
huzibaca 3b8147b6ad chore: typo warn -> warning 2024-11-18 06:01:51 +08:00
huzibaca 221b732472 refactor: logger fetch logic 2024-11-18 05:58:06 +08:00
huzibaca 132641f269 fix: the CJS build of Vite's Node API is deprecated, part 2 2024-11-18 01:21:00 +08:00
huzibaca d8cf585fc5 fix: the CJS build of Vite's Node API is deprecated 2024-11-18 01:07:16 +08:00
Christine. 7f0d9952aa chore: Replace test URL to support iPv4&iPv6 (#2033) 2024-11-18 00:20:21 +08:00
huzibaca 415b4879f1 fix: webdav list interface compatibility issue 2024-11-17 23:57:28 +08:00
huzibaca 7e5d6ef9b6 fix: webdav refreshes data and clears the original data when an error occurs. 2024-11-17 23:50:34 +08:00
huzibaca ff297957c7 chore: update 2024-11-17 01:01:36 +08:00
huzibaca 7489f5e62d chore: update 2024-11-17 00:46:35 +08:00
huzibaca 3575c16326 fix: fixes #1968 2024-11-16 23:09:10 +08:00
wonfen 04f4f5f713 release rc.7 2024-11-16 10:15:50 +08:00
huzibaca eb78af7c77 chore: remove debug code 2024-11-16 07:35:34 +08:00
huzibaca 114d5ab541 fix: fixes #1940 2024-11-16 06:08:49 +08:00
huzibaca f13140e39e fix: When the shortcut key closes the window, the window is minimized 2024-11-16 04:46:20 +08:00
huzibaca 97717c648e fix: windows arm64 vsruntime is not installed 2024-11-16 03:12:11 +08:00
huzibaca ec6705eaf4 Merge branch 'fix-tray-menu-flash-on-windows' into fix-migrate-tauri2-errors
* fix-tray-menu-flash-on-windows:
  chore: update
2024-11-16 02:54:24 +08:00
Chenx Dust 1e11eff811 feat: support mptcp and smux display (#1995)
Corresponding pull request in mihomo: https://github.com/MetaCubeX/mihomo/pull/1646
2024-11-16 01:35:22 +08:00
huzibaca 13fb8c5d7d chore: update 2024-11-15 17:19:14 +08:00
huzibaca e87e06dd37 chore: unified code format 2024-11-14 03:21:18 +08:00
huzibaca b9bb79b4dc fix: call parameter error 2024-11-14 03:16:32 +08:00
wonfen d0f907f349 chore: update info & i18n 2024-11-14 03:01:37 +08:00
huzibaca 867c83b84c chore: update dep(tauri-bubild) 2024-11-13 21:49:53 +08:00
huzibaca 632d389411 chore: update deps 2024-11-13 21:39:55 +08:00
huzibaca b4df1b6e42 feat: add refresh button 2024-11-13 00:53:52 +08:00
huzibaca 176620e3bb fix: InputProps is deprecated 2024-11-13 00:30:30 +08:00
huzibaca c37f22dc65 fix: mui grid has been deprecated 2024-11-13 00:21:22 +08:00
huzibaca 9d9cf27460 fix: application restart, window status not saved 2024-11-12 22:49:08 +08:00
huzibaca 6e8def3ef7 fix: syntax issues caused by upgrading mui5 2024-11-12 20:05:28 +08:00
huzibaca ec0d872f11 chore: update 2024-11-12 19:44:57 +08:00
huzibaca 9e4a8708e7 fix: import error caused by dependency upgrade 2024-11-12 19:35:56 +08:00
huzibaca a681fdeee1 fix: undefined error 2024-11-12 19:34:53 +08:00
huzibaca 8763a76475 chore: update deps 2024-11-12 19:06:04 +08:00
wonfen 4306fba997 release rc.6 2024-11-12 03:49:21 +08:00
huzibaca 3759239dac feat: add webdav backup 2024-11-12 02:55:02 +08:00
huzibaca 4ec0b1d6e4 chore: update 2024-11-09 12:13:50 +08:00
huzibaca 6271726f07 Merge branch 'feat-webdav-backup' of ssh://github.com/clash-verge-rev/clash-verge-rev into feat-webdav-backup
# Conflicts:
#	src/locales/fa.json
#	src/locales/ru.json
#	src/locales/zh.json
2024-11-09 11:54:33 +08:00
wonfen 26dd097962 update: Readme 2024-11-09 09:00:53 +08:00
wonfen e5740579f4 fix: translation 2024-11-09 08:59:30 +08:00
huzibaca ca97e3a3e6 chore: update 2024-11-09 06:56:58 +08:00
huzibaca d3f6822080 chore: update 2024-11-09 05:50:51 +08:00
huzibaca 9d9a6dfddb chore: update 2024-11-08 23:42:00 +08:00
huzibaca c3e24d7b96 chore: update 2024-11-08 21:46:15 +08:00
huzibaca 20d163cf3a Merge branch 'feat-add-unified-delay' into fix-migrate-tauri2-errors 2024-11-06 13:56:36 +08:00
huzibaca cf90f3abc9 chore: update 2024-11-06 13:52:36 +08:00
huzibaca 8871660f0e chore: update 2024-11-06 10:07:02 +08:00
huzibaca 97a030c22e fix: @use rules must be written before any other rules. 2024-11-06 09:48:31 +08:00
huzibaca 8a6ce0f0db fix: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0. 2024-11-06 09:46:17 +08:00
huzibaca 285b6b9287 chore: update 2024-11-05 21:40:02 +08:00
huzibaca 3515cc8e63 chore: update 2024-11-05 21:35:17 +08:00
huzibaca b0132c9718 chore: update 2024-11-05 18:38:50 +08:00
huzibaca ef84c31761 chore: update 2024-11-05 18:30:28 +08:00
huzibaca 4953d4b4d0 chore: add tooltip 2024-11-05 17:39:59 +08:00
huzibaca 2833718c90 feat: add unified delay 2024-11-05 16:24:58 +08:00
huzibaca ab7775e1ef fix: wrong window state save point 2024-11-04 09:53:40 +08:00
huzibaca a5c4562f59 chore: update deps 2024-11-02 10:25:38 +08:00
huzibaca 00bd5b0e1e chore: update 2024-11-02 07:27:08 +08:00
huzibaca b66c07bd96 chore: update (+1 squashed commit)
Squashed commits:
[78899ef] fix: bbr2 not working in windows 11(24H2)
2024-11-02 07:10:47 +08:00
huzibaca d8d0a59371 chore: update 2024-11-01 06:03:01 +08:00
The1111mp 953153ea1a chore: added support for compiling linux arm architecture (#1945)
* chore: added support for compiling linux arm architecture

Signed-off-by: The1111mp <The1111mp@outlook.com>

* chore: remove debug code

Signed-off-by: The1111mp <The1111mp@outlook.com>

---------

Signed-off-by: The1111mp <The1111mp@outlook.com>
2024-10-31 17:20:16 +08:00
downerand周晓亮 fc03ce1247 fix: fix TUN icon is overwritten by system proxy icon (#1961)
Co-authored-by: 周晓亮 <zhouxl@jiyitech.com>
2024-10-31 09:07:21 +08:00
huzibaca 08bf70681c feat: use tauri_plugin_window_state 2024-10-30 18:52:53 +08:00
huzibaca 00bc6a6301 revert: feat: use tauri-plugin-persisted-scope 2024-10-30 18:28:55 +08:00
huzibaca c5916cf5ec feat: use tauri-plugin-persisted-scope 2024-10-30 16:37:47 +08:00
huzibaca 4213a5fad1 chore: window hide replaces window minimize 2024-10-30 13:51:58 +08:00
wonfen 17fc9cf0eb release: rc.5 2024-10-30 13:08:37 +08:00
wonfen eb1a1b3786 chore: update bug report template 2024-10-30 12:59:19 +08:00
huzibaca dce72a16f0 chore: remove useless hooks
the window is not closed, it is minimized, so the position still exists
2024-10-30 10:06:54 +08:00
huzibaca 7550c2321c fix: linux file permissions problem
check-config configuration file moved from temp_dir to home_dir, resolved
2024-10-30 09:08:44 +08:00
huzibaca f0c4360210 chore: restore linux custom close button 2024-10-30 06:30:49 +08:00
huzibaca d10323701a fix: performance issues caused by closing windows on mac 2024-10-30 06:27:29 +08:00
huzibaca 94c437c3e3 chore: update 2024-10-29 10:15:07 +08:00
huzibaca 202fb19ab1 chore: update 2024-10-29 10:13:53 +08:00
huzibaca 2bb3498602 chore: disable websocket logging by default to reduce performance consumption. 2024-10-29 09:19:21 +08:00
huzibaca 9d476d7add fix incorrect usage of useCustomTheme 2024-10-29 09:07:08 +08:00
huzibaca 014829e69a chore: optimised the logic of dns processing 2024-10-28 13:09:55 +08:00
huzibaca a127cd6444 fix: dns not restored when exiting the app 2024-10-28 00:24:57 +08:00
Christine. 486ffbdc08 add more contribution details for Windows (#1939)
* doc: add more building details

Some friends said the build failed.

* chore: replace test url

replace default delay check URL, some user say that the delay numbers seen in CVR are very different from those in other software, and this default test result is not a valid reference.
2024-10-27 13:00:47 +08:00
huzibaca 526c5bdd91 fix: unused variable 2024-10-27 10:19:42 +08:00
huzibaca db11651f85 chore: update 2024-10-27 06:47:16 +08:00
huzibaca 4ce7d9f7a9 feat: macos service installation query, can't avoid it, add service run check, if installation not confirmed, exit automatically after 10 seconds 2024-10-27 06:32:25 +08:00
huzibaca df3c296850 Revert "chore: update resource"
This reverts commit e3c52781f3.
2024-10-27 05:10:26 +08:00
huzibaca 5ef9220175 chore: update 2024-10-27 05:06:42 +08:00
huzibaca e3c52781f3 chore: update resource 2024-10-27 04:46:24 +08:00
huzibaca 1cb1946497 chore: update deps 2024-10-27 01:24:13 +08:00
huzibaca 9e486e6836 chore: update 2024-10-26 19:23:49 +08:00
wonfen e5dc4a55b0 release rc.4 2024-10-26 12:05:09 +08:00
huzibaca 76d04805a4 fix:public DNS not set in macos, tun+fake-ip 2024-10-26 08:55:00 +08:00
huzibaca 652a20c523 chore: update 2024-10-26 01:44:06 +08:00
huzibaca 6c2af1bef5 chore: update 2024-10-25 21:25:06 +08:00
huzibaca ba2aa9d6e7 chore: update 2024-10-25 14:13:30 +08:00
huzibaca a1c72fe780 chore: update 2024-10-25 14:12:27 +08:00
huzibaca 197f9fd964 fix: the tray icon does not display the correct colours on macos. 2024-10-25 13:58:22 +08:00
wonfen fc2c10bb3f chore: add mac sun ico 2024-10-25 12:27:22 +08:00
wonfen d7dd797494 release 2.0rc3 2024-10-24 12:52:20 +08:00
huzibaca 4f954657a1 chore: update 2024-10-24 11:14:18 +08:00
huzibaca 3eddcafeb6 Revert "chore: global mode doesn't show proxy groups, use placeholder images instead"
This reverts commit 3df4718709.
2024-10-24 07:44:21 +08:00
huzibaca 3df4718709 chore: global mode doesn't show proxy groups, use placeholder images instead 2024-10-24 07:30:30 +08:00
huzibaca b5fddf8fa1 chore: update 2024-10-24 06:58:21 +08:00
huzibaca fdc8a83f25 fix: proxy view display error 2024-10-24 06:54:27 +08:00
huzibaca e79121e46a refactor: the logic of profiles activation 2024-10-24 05:02:47 +08:00
huzibaca aab71e55de fix: restart app failure 2024-10-24 02:54:57 +08:00
huzibaca caf4ee6863 fix: tun mode switch is not effective 2024-10-24 02:16:28 +08:00
huzibaca 30b0c45539 refactor: IRuntime::patch_config() 2024-10-23 10:34:14 +08:00
huzibaca 2ab75db9c9 chore: update 2024-10-23 09:26:14 +08:00
huzibaca f9009de894 chore: update 2024-10-23 05:48:01 +08:00
huzibaca da8ed1ac5a fix: failed to install service on macos 2024-10-23 04:46:47 +08:00
huzibaca afde7e4a44 feat: add windows uninstall script 2024-10-22 04:46:08 +08:00
huzibaca 3e94adb4b1 feat: add linux uninstall script 2024-10-22 03:17:04 +08:00
huzibaca 5c577e533f fix: debian executable file has no permissions 2024-10-22 02:37:36 +08:00
huzibaca c64b9d5a2b chore: update 2024-10-22 01:34:26 +08:00
huzibaca 545ea16ae8 fix: change the window close to minimize the window.
there is currently an error in the tauri library.
2024-10-21 23:16:45 +08:00
huzibaca bf245a4ee1 fix: pac url error ,fixes #1889 2024-10-21 22:52:10 +08:00
wonfen b28fc26ae6 chore: update bug_report template 2024-10-21 11:30:07 +08:00
huzibaca 750dfdb3a8 Merge branch 'fix-migrate-tauri2-errors' of ssh://github.com/clash-verge-rev/clash-verge-rev into fix-migrate-tauri2-errors
* 'fix-migrate-tauri2-errors' of ssh://github.com/clash-verge-rev/clash-verge-rev:
  chore: update to 2.0 rc2
2024-10-21 04:35:42 +08:00
huzibaca 5b93c2b907 chore: update 2024-10-21 04:35:21 +08:00
wonfen bbd72a79cb chore: update to 2.0 rc2 2024-10-21 03:15:02 +08:00
huzibaca babf5d840c chore: replace sudo with pkexec execution 2024-10-21 02:31:43 +08:00
huzibaca 90091db3b1 chore: update 2024-10-21 02:17:41 +08:00
huzibaca ef03dc12bb chore: update 2024-10-21 01:35:28 +08:00
huzibaca a35014f0b5 chore: update 2024-10-21 01:30:13 +08:00
huzibaca abb0a049a8 chore: update 2024-10-21 01:12:52 +08:00
huzibaca 3de18e2a86 chore: update 2024-10-21 00:50:43 +08:00
huzibaca 399bbd56ec chore: update deps 2024-10-21 00:45:06 +08:00
huzibaca 38999d284a chore: update 2024-10-21 00:34:28 +08:00
huzibaca b61b63d100 fix: failed to start system proxy with PAC mode 2024-10-20 23:13:23 +08:00
huzibaca 0dc92fbc99 chore: update deps 2024-10-20 22:35:16 +08:00
huzibaca 1cb7cd8859 Merge branch 'fix-linux-errors' into fix-migrate-tauri2-errors
* fix-linux-errors:
  chore: try to fix service not started on linux
2024-10-20 21:57:41 +08:00
huzibaca 199700cf09 fix: mac commad+q global hijack is not released 2024-10-20 06:06:47 +08:00
huzibaca ee78b7898b chore: try to fix service not started on linux 2024-10-20 04:48:42 +08:00
huzibaca a47ded7cf2 chore: update 2024-10-18 22:50:03 +08:00
huzibaca 770f031b8b fix: try to fix service not started on linux 2024-10-18 07:08:34 +08:00
wonfen 18139c7114 chore: adjust translation 2024-10-17 05:09:00 +08:00
huzibaca 4a64654f49 chore: update deps 2024-10-16 22:49:24 +08:00
huzibaca 377ff279b8 chore: update 2024-10-16 02:55:23 +08:00
huzibaca d5d39e1c2a fix: system proxy cannot be closed on mac 2024-10-16 02:52:48 +08:00
huzibaca f7b1da7557 chore: update 2024-10-16 01:55:22 +08:00
huzibaca 5e8c6c6b23 chore : update version 2024-10-16 01:53:33 +08:00
huzibaca 64634ef07b chore: t emporarily cancel window closure and change to minimize 2024-10-16 01:53:16 +08:00
huzibaca 7de2474611 Merge branch 'fix-migrate-tauri2-errors' of ssh://github.com/clash-verge-rev/clash-verge-rev into fix-migrate-tauri2-errors 2024-10-15 02:38:57 +08:00
huzibaca ea2b6a9ad8 fix: path escape issue 2024-10-15 02:35:50 +08:00
wonfen 6ee7be7cae chore: update changelog and texts 2024-10-15 02:32:25 +08:00
huzibaca 12690ed464 chore: remove windows esc shortcut key 2024-10-14 13:45:26 +08:00
huzibaca 90361242ac chore: update 2024-10-13 03:01:32 +08:00
huzibaca cd8daa929a chore: update deps 2024-10-13 02:55:35 +08:00
huzibaca a07c579497 chore: adjust tun default parameters 2024-10-13 02:42:37 +08:00
huzibaca e78f619be1 fox: external-controller cors error 2024-10-11 22:01:57 +08:00
huzibaca 5e7ff36e8e chore: complete field identifier 2024-10-11 14:33:11 +08:00
huzibaca e7e0caaa41 Merge branch 'main' into fix-migrate-tauri2-errors 2024-10-11 02:56:30 +08:00
Lvc Revincx 00c6e41239 fix: task bar icon misssing under linux wayland (#1816) 2024-10-11 02:52:57 +08:00
huzibaca c604ae38dd fix: installation service error 2024-10-11 00:59:34 +08:00
huzibaca 25e25c2285 chore: update 2024-10-10 23:34:55 +08:00
huzibaca b4180b5b48 chore:update 2024-10-10 18:52:20 +08:00
huzibaca d14bda7e7a chore: update 2024-10-10 18:40:39 +08:00
huzibaca 045ddc5ca5 chore: update 2024-10-10 02:21:22 +08:00
huzibaca 3f3fad0db7 feat: Modify startup logic and install services by default 2024-10-10 00:34:36 +08:00
huzibaca 30c77b891d chore: update 2024-10-09 01:14:03 +08:00
huzibaca 5e126364ed refactor: core logic 2024-10-08 02:39:17 +08:00
huzibaca 7f2f9588ac chore: update 2024-10-06 02:03:32 +08:00
huzibaca e299e246e3 chore: revert 2024-10-06 01:09:59 +08:00
huzibaca 0541a0c69f refactor: tun mode is turned on and off, does not depend on services, and does not affect other configurations 2024-10-05 22:45:59 +08:00
huzibaca 1178f7c892 refactor: init_log 2024-10-05 12:19:44 +08:00
huzibaca a3e78bd76d refactor: get_bypass func 2024-10-05 02:58:41 +08:00
huzibaca bcf33b779e chore: update 2024-10-05 00:03:11 +08:00
huzibaca d0ba506011 chore: update deps 2024-10-04 23:56:50 +08:00
huzibaca 477d1f2aee feat: add color to log 2024-10-04 22:38:06 +08:00
huzibaca aa4b1ad8e8 chore: update 2024-10-04 05:37:53 +08:00
huzibaca 6b902a7ad8 chore: update 2024-10-04 05:27:59 +08:00
huzibaca a626f2ce29 chore: update 2024-10-03 14:31:40 +08:00
huzibaca 2685a02e1a chore: update 2024-10-03 12:01:06 +08:00
huzibaca bf1f201d4b feat: windows uses sysproxy.exe for system proxy 2024-10-03 02:09:22 +08:00
huzibaca 335578dd91 refactor: simplify sysproxy logic
1. close all proxies directly when reset_proxy
2. init_sysproxy and update_sysproxy combined into one
3. optimize lock usage
4 ptimize the thread loop of guard_sysproxy,
2024-10-03 01:31:37 +08:00
huzibaca 3a3fc986c4 chore: replace sysproxy-rs repo 2024-10-02 17:43:14 +08:00
huzibaca 0e1e54aff4 feat: test item, when icon is svg, add svg format check 2024-10-01 20:49:03 +08:00
huzibaca 0b8f24a92e chore: optimize lock 2024-10-01 00:27:08 +08:00
huzibaca 3cbcd4630c chore: remove the manual release lock 2024-09-30 23:07:46 +08:00
wonfen 709ab3dd88 chore: add service mode info 2024-09-30 01:04:15 +08:00
huzibaca b3d93d0761 chore: update 2024-09-28 20:20:40 +08:00
huzibaca 79d1539149 refactor: logic optimization 2024-09-28 12:37:01 +08:00
huzibaca ba1a7e0fd6 ,fix: when updating the verge configuration, notification error 2024-09-27 22:25:23 +08:00
wonfen 30ebfd84cf chore: update bug report template 2024-09-27 14:08:50 +08:00
huzibaca aef215d810 chore: update 2024-09-27 10:05:31 +08:00
huzibaca cee872e944 chore: remove compatibility code 2024-09-27 10:04:23 +08:00
huzibaca c50e8f9de8 refactor: logic optimization 2024-09-27 00:24:05 +08:00
huzibaca 11a8e3465f chore: update 2024-09-26 19:47:25 +08:00
huzibaca e8ed7c1e3e chore: remove compatibility code 2024-09-26 19:47:00 +08:00
huzibaca 5b822d238d chore: update 2024-09-26 19:23:16 +08:00
huzibaca 053754fea8 chore: update 2024-09-26 12:20:57 +08:00
huzibaca 7a9fd118a3 chore: update 2024-09-25 21:20:36 +08:00
huzibaca 2aa3c8cfe1 chore: update 2024-09-25 21:07:01 +08:00
huzibaca ec2bf37ad4 Revert "chore: cover panic error"
This reverts commit b8cb48aead.
2024-09-25 20:22:49 +08:00
huzibaca fc5c959a55 chore: unified hotkey registration 2024-09-25 15:45:12 +08:00
huzibaca b8cb48aead chore: cover panic error 2024-09-25 15:44:05 +08:00
huzibaca 4eccedcd78 fix: the save_indow_size_position method has been triggered twice, at the same time
1. remove windowEvent::destoryed  hook
2024-09-25 13:54:05 +08:00
huzibaca 08b1160d63 chore: remove useless exit codes 2024-09-25 11:47:01 +08:00
huzibaca 3bcd8b8b2c fix: code lint 2024-09-24 20:11:33 +08:00
huzibaca 4b20b65a22 fix: code lint 2024-09-24 20:09:30 +08:00
huzibaca 4b234a3211 fix: code lint 2024-09-24 20:06:25 +08:00
huzibaca 2ca7310baf chore: add cross-env dev dependency 2024-09-24 16:35:19 +08:00
huzibaca 5d742a9037 chore: update 2024-09-23 23:57:08 +08:00
huzibaca a3465d292c refactor: unify and simplify the call of app_handle(2) 2024-09-23 23:15:51 +08:00
huzibaca 8a3a094414 chore: update 2024-09-23 17:15:28 +08:00
huzibaca 52d5f2c992 chore: update 2024-09-23 16:53:00 +08:00
huzibaca 60e0f972d0 refactor: unify and simplify the call of app_handle 2024-09-23 16:31:58 +08:00
huzibaca 0a9182519d chore: update 2024-09-22 22:15:36 +08:00
huzibaca bfded924d7 chore: rename method 2024-09-22 20:29:43 +08:00
huzibaca c653c458b9 Revert "chore: update"
This reverts commit 4b7ffa1465.
2024-09-22 16:46:24 +08:00
huzibaca 4b7ffa1465 chore: update 2024-09-22 15:59:59 +08:00
huzibaca a5c871e933 chore: update 2024-09-22 15:39:52 +08:00
huzibaca 56983c4d2a chore: update 2024-09-22 14:08:08 +08:00
huzibaca e9721ecc4d Revert "chore: remove useless exit codes and hooks"
This reverts commit 40ff3fd4bf.
2024-09-22 00:41:20 +08:00
huzibaca 84fe9c84a8 fix: remove unused variable 2024-09-22 00:32:40 +08:00
huzibaca 40ff3fd4bf chore: remove useless exit codes and hooks 2024-09-21 21:05:34 +08:00
huzibaca e92074e586 chore: try adjusting the triggering of the tray mouse button 2024-09-21 15:38:14 +08:00
huzibaca 424c1f7d8f chore : update 2024-09-20 18:02:19 +08:00
huzibaca 868707dbde fix: tray menu text was not aligned 2024-09-20 17:59:24 +08:00
huzibaca 29d54fe589 chore: update 2024-09-20 17:49:31 +08:00
huzibaca c246ae3bcf Fix: Custom tray icon not working
1. Contains potential deadlock
2024-09-20 16:26:23 +08:00
huzibaca ab887c656a fix: function parameter call error 2024-09-20 08:27:06 +08:00
Sukka eefeec7f0a chore: drop fs-extra (#1739)
nb!
2024-09-20 02:01:36 +08:00
huzibaca 74777df344 chore: update dev dependencis 2024-09-18 21:34:20 +08:00
huzibaca c21ce578f5 fix: exit hotkey conflict 2024-09-18 15:16:43 +08:00
huzibaca 7d1b7adda5 fix: whether the window starts as fullscreen or not. 2024-09-18 13:23:27 +08:00
huzibaca f64528dd87 chore: revert 2024-09-18 12:24:52 +08:00
huzibaca 3ca1d03d34 refactor: exit app 2024-09-18 02:48:55 +08:00
huzibaca 49929bd473 fix: the shortcut key to exit cannot be used 2024-09-18 01:41:46 +08:00
huzibaca 25f15e12cf fix: hotkey status not accurately processed, resulting in two triggers 2024-09-17 10:59:39 +08:00
huzibaca b6d15c27b6 chore: update 2024-09-17 08:28:13 +08:00
huzibaca 2f8c5c9b20 chore: update 2024-09-17 08:02:29 +08:00
huzibaca bee1373f92 chore: add window to allow full screen configuration 2024-09-16 15:25:01 +08:00
huzibaca 8a4535d55e chore: update 2024-09-16 07:08:22 +08:00
huzibaca 613fb60859 chore: update 2024-09-16 06:43:25 +08:00
huzibaca 18f9978fcf chore: update 2024-09-16 06:37:39 +08:00
huzibaca 9b527cb53c chore: update 2024-09-16 06:17:22 +08:00
huzibaca c06e4af79d chore: update 2024-09-16 05:50:05 +08:00
huzibaca e89c3ed65e chore: modify application description definition 2024-09-16 05:38:52 +08:00
huzibaca eb60cfb083 fox: try to restore the processing logic of windows custom scheme 2024-09-16 05:30:29 +08:00
huzibaca 65ce7429b7 chore: update dev workflow 2024-09-15 07:57:34 +08:00
huzibaca 4b4a4927c4 chore: add log 2024-09-15 07:03:04 +08:00
huzibaca f57d2df5ec chore: follow official standards and adjust the main file 2024-09-15 06:24:53 +08:00
wonfen d70e18ba8d chore: update change log and texts 2024-09-15 00:33:54 +08:00
huzibaca 962aeb1c75 fix: when importing subscriptions, force the window to open 2024-09-14 10:03:19 +08:00
huzibaca 6d7711f3ea fix: after importing a subscription, it cannot be automatically switched to the current subscription. 2024-09-13 18:17:14 +08:00
huzibaca ba487611ee chore: update action(rust-toolchain) 2024-09-13 14:21:51 +08:00
huzibaca 6a636444a7 chore: update action(rust-toolchain) 2024-09-13 14:14:27 +08:00
wonfen 25c766ed94 chore: change folder name 2024-09-13 12:03:28 +08:00
huzibaca dbc3723bdc feat: Added web notification of successful subscription import 2024-09-13 11:24:49 +08:00
huzibaca 3963c60fc1 fix: when the service is not installed, the tray hides the Tun mode 2024-09-13 04:12:25 +08:00
huzibaca b888c7729e refactor: url scheme implementation 2024-09-13 03:21:55 +08:00
huzibaca a39ece6156 chore: delete useless emebd server api 2024-09-12 19:01:08 +08:00
huzibaca 624eb5b085 fix:tray click event 2024-09-12 17:02:17 +08:00
huzibaca 9ec8c903c5 feat: Optimize kernel startup logic
1. tun mode startup logic
2. Remove invalid creation process PID logic
2024-09-12 15:35:08 +08:00
huzibaca 2396a6b35a feat: log panic 2024-09-12 07:59:51 +08:00
huzibaca 1fc54e49d9 chore: update deps 2024-09-12 06:14:23 +08:00
huzibaca 30ee7a9e85 fix: missing items in system tray 2024-09-12 05:45:15 +08:00
huzibaca b9be5e8c44 feat: migrate tauri 2.0 2024-09-11 08:15:03 +08:00
huzibaca ae1c6d4617 chore: update 2024-09-04 07:53:16 +08:00
huzibaca 7b11e157c4 chore: update capabilities 2024-09-04 05:57:37 +08:00
wonfen 5783b7b392 chore: 2.0.0 beta release 2024-09-03 00:00:12 +08:00
huzibaca 507d52bcb9 chore: update 2024-09-02 19:33:17 +08:00
wonfen d45929c604 chore: use in-house update proxy 2024-08-24 02:02:17 +08:00
wonfen 632c32addb Style: fix macos new version btn position 2024-08-22 07:33:51 +08:00
wonfen 7d2b18759e Release 1.7.7 2024-08-22 03:47:33 +08:00
wonfen aa8f6f4ad6 chore: Shorten text to prevent Windows tooltip truncation. 2024-08-22 03:41:16 +08:00
wonfen 592167ffb7 fix: profiles will not be selected after import 2024-08-22 00:57:04 +08:00
wonfen 12a6bfad00 Release 1.7.6 2024-08-18 00:49:23 +08:00
tony-sung 1ea3b956ad Update web-ui-viewer.tsx Fix yacd parameter hostname error (#1484) 2024-08-18 00:23:24 +08:00
MystiPanda 1b7dafe743 feat: auto select profile 2024-07-30 09:06:12 +08:00
MystiPanda 624eb2a2ba chore: prepend new added rules 2024-07-24 14:25:06 +08:00
MystiPanda 88d98517c8 fix: wrap the password in quotes
#1460
2024-07-24 14:22:23 +08:00
Avan 9528606906 style: AddressDisplay padding (#1457) 2024-07-23 22:17:51 +08:00
nhsmw 09b44a3af3 fix: update test url 2024-07-19 10:32:41 +08:00
MystiPanda dc861eca7d chore: remove unnecessary console log 2024-07-18 23:37:54 +08:00
MystiPanda e749fe70e2 fix: use group testUrl
#1384
2024-07-18 15:04:55 +08:00
MystiPanda d20c082deb fix: windows x86 updater url 2024-07-18 14:38:31 +08:00
MystiPanda c4abc3ade3 fix: error command status 2024-07-16 18:37:56 +08:00
MystiPanda ff28aab56e fix: MacOS service install error 2024-07-16 13:01:27 +08:00
MystiPanda 1a6f842492 fix(#1335): support cidr 2024-07-16 12:22:11 +08:00
MystiPanda af0cd4a342 fix: try to fix install service 2024-07-16 12:07:23 +08:00
MystiPanda d98b3224cf chore: disable okBtn 2024-07-15 23:38:49 +08:00
MystiPanda f2e5f0754c ci: fix winget uploader 2024-07-15 23:27:38 +08:00
MystiPanda ec1601f317 Release 1.7.5 2024-07-15 22:35:33 +08:00
MystiPanda 54116608dc ci: fix winget uploader 2024-07-15 22:33:57 +08:00
MystiPanda fea6735ea1 fix: copy necessary files 2024-07-15 22:25:04 +08:00
MystiPanda 2f77a07b3d Release 1.7.4 2024-07-15 20:56:18 +08:00
MystiPanda 4cd7ccda57 fix: unified switch style 2024-07-15 20:42:00 +08:00
MystiPanda ef337ffb69 feat: optimize the service mode interaction logic. 2024-07-15 20:02:05 +08:00
MystiPanda 9ff5d8d4e0 Revert "fix: try to fix abnormal stuck"
This reverts commit e14ddbe23c.
2024-07-14 12:49:06 +08:00
Sukka 199a5eed60 fix(#1387): avoid catastrophic backtracking (#1396) 2024-07-14 12:26:02 +08:00
MystiPanda e14ddbe23c fix: try to fix abnormal stuck
#1387
2024-07-13 21:41:04 +08:00
MystiPanda 3878e66ea4 chore: update locale 2024-07-13 19:15:07 +08:00
MystiPanda c14a33444b chore: unified icon style 2024-07-13 19:01:16 +08:00
MystiPanda 48f7c15035 feat: display network interface 2024-07-13 14:15:13 +08:00
Avan 32212a46e2 feature: copy clash env (#1391)
* feat: copy clash env

* style: use ContentCopyRounded replace CopyAll
2024-07-13 01:03:46 +08:00
Avan b4025c45da style: EditorViewer title align center (#1390) 2024-07-12 23:29:55 +08:00
MystiPanda bd589c4422 fix: service install failed on macos 2024-07-12 20:54:31 +08:00
dongchengjie f85f7758e6 chore: checkbox items' title & button icons 2024-07-12 18:24:19 +08:00
dongchengjie 49e36b6e00 chore: editor resizing debounce 2024-07-08 23:18:06 +08:00
dongchengjie df6e245e5c chore: remove dialog overflow-scroll 2024-07-08 22:07:43 +08:00
dongchengjie 4e4f9af5b7 fix: editor fails to resize after toggling on macos 2024-07-08 21:40:56 +08:00
MystiPanda aa9e63b51a fix dev.yml 2024-07-08 16:17:23 +08:00
MystiPanda 82340582b2 ci: add dev workflow 2024-07-08 13:20:36 +08:00
Akioe Yu 9d045e24db fix: dnd box z-index (#1353)
* fix: dnd box z-index

* fix: dnd boxes
2024-07-08 10:49:57 +08:00
MystiPanda 1dcc95d16a perf: optimized response speed 2024-07-08 00:29:49 +08:00
dongchengjie 78c32d51db chore: group types locale 2024-07-07 21:56:20 +08:00
❤是纱雾酱哟~ 545a2c6688 ci: Integrate "Winget Releaser" (#1326) 2024-07-07 20:42:17 +08:00
MystiPanda 359d9285fe perf: change port too slow 2024-07-07 18:22:02 +08:00
MystiPanda 1bd51be99c feat: get network interface 2024-07-07 18:13:10 +08:00
dongchengjie ee56080af0 fix: usage percent style 2024-07-07 18:08:02 +08:00
MystiPanda 18196c4a77 fix: trojan uri parser 2024-07-07 11:47:49 +08:00
MystiPanda 14cd3b99cc fix: tray icon size
#1341
2024-07-07 11:18:22 +08:00
MystiPanda ff75fe47ee chore: disable browser autocomplete 2024-07-07 11:16:59 +08:00
MystiPanda 7e2cc180c3 Release 1.7.3 2024-07-06 18:48:25 +08:00
MystiPanda bef9eea87b fix: bypass reg error
#1335
2024-07-06 18:23:23 +08:00
MystiPanda 9cd1aef1db fix: limite cipher types 2024-07-06 10:49:42 +08:00
MystiPanda ab25cf0637 fix: rule parser 2024-07-06 10:35:29 +08:00
MystiPanda 32f37720dd Revert "chore: add base64 decode step"
This reverts commit 38eb3123be.
2024-07-06 10:12:51 +08:00
MystiPanda 766cf3aeae chore: decode base64 2024-07-06 10:12:40 +08:00
MystiPanda 905353d540 fix: some error 2024-07-06 09:54:16 +08:00
MystiPanda 38eb3123be chore: add base64 decode step 2024-07-06 00:57:38 +08:00
MystiPanda 0183edd450 fix: style 2024-07-06 00:45:21 +08:00
MystiPanda 2f640a946e fix: type 2024-07-06 00:23:52 +08:00
MystiPanda ec6c2adf9b chore: Improve URI parser 2024-07-05 22:44:05 +08:00
MystiPanda 5a1edc5ffb feat: finish rpoxies editor 2024-07-05 19:49:32 +08:00
MystiPanda 149d482c7d fix: groups config type error
feat(unfinished): add proxy editor
2024-07-05 00:38:50 +08:00
MystiPanda c9f784e4fa feat: add profile name param for script 2024-07-04 23:11:54 +08:00
MystiPanda 9edafa75e7 build: remove unused resource file 2024-07-04 22:34:48 +08:00
MystiPanda 08baba545d chore: disable unnecessary ports by default 2024-07-04 18:58:34 +08:00
MystiPanda 60d0b29236 feat: support cache for groups editor 2024-07-04 18:53:39 +08:00
MystiPanda f5ee4fb5b5 chore: unified style 2024-07-04 18:50:21 +08:00
MystiPanda 5b7b3be6f9 fix: useseq error 2024-07-04 14:30:34 +08:00
MystiPanda c933560102 fix: locale typo 2024-07-04 14:18:21 +08:00
MystiPanda 12a80f35fe feat: support visual edit for proxy group 2024-07-04 14:13:19 +08:00
MystiPanda 43f901eeb9 fix: rules editor get groups error 2024-07-04 11:03:51 +08:00
MystiPanda e8440e06a1 feat: support get merged rule-set name 2024-07-03 22:13:24 +08:00
dongchengjie 19e9e9d032 fix: remove rule condition where is not required 2024-07-03 17:03:12 +08:00
MystiPanda 8ff2d687e4 Release 1.7.2 2024-07-03 13:26:06 +08:00
MystiPanda e616e8f9aa fix: Insertion order
#1300
2024-07-03 13:13:54 +08:00
MystiPanda 1fbc67fe98 fix: dialog styles 2024-07-03 13:13:29 +08:00
MystiPanda a49e4d8d0c fix: panic 2024-07-03 12:49:29 +08:00
MystiPanda fa4ac00504 feat: support edit rules file 2024-07-03 12:37:08 +08:00
MystiPanda dc066edec4 fix: edit groups error 2024-07-03 10:58:57 +08:00
MystiPanda eaed7b2899 Release 1.7.1 2024-07-03 09:52:19 +08:00
MystiPanda 123ecc3548 fix: rules drag error 2024-07-03 09:45:14 +08:00
MystiPanda c857fcf035 fix: compatibility 2024-07-03 08:15:13 +08:00
dongchengjie 923f710e53 fix: search-box takes no effect in rule-editor #1288 2024-07-03 03:08:01 +08:00
dongchengjie 8d8c2ed262 fix: sub usage percent fails to display when number is too small #1290 2024-07-03 02:59:32 +08:00
MystiPanda 8558673a5a Release 1.7.0 2024-07-03 00:32:14 +08:00
MystiPanda 7372f330a4 perf: optimize performance of the rule editor 2024-07-02 23:55:29 +08:00
dongchengjie 07c145c661 feat: editor add tool-tar buttons 2024-07-02 23:24:44 +08:00
dongchengjie f69e1d2a0c refactor: editor-viewer using react-monaco-editor 2024-07-02 21:02:29 +08:00
dongchengjie 0021fc24bb chore: bump schema to 1.18.5-alpha7 2024-07-02 19:38:44 +08:00
MystiPanda 82bc1d5da5 chore: disable autocomplete 2024-07-02 19:02:05 +08:00
MystiPanda ffc343b471 fix: default value of global extend config 2024-07-02 18:46:23 +08:00
dongchengjie 86e43123b0 chore: add rule list search-box 2024-07-02 17:32:49 +08:00
dongchengjie b558202441 chore: rule types locale 2024-07-02 17:04:22 +08:00
MystiPanda 83947b6725 chore: check the validity of the rule content 2024-07-02 13:11:54 +08:00
MystiPanda 7e8b65e61f chore: Adjust the chain processing execution order and default value 2024-07-02 12:40:28 +08:00
MystiPanda cff0ea425c build: update depends 2024-07-02 12:21:17 +08:00
MystiPanda f6ba6d0310 chore: update locale 2024-07-01 23:30:14 +08:00
MystiPanda 7b9bf9e552 feat: allow set bypass without using default value 2024-07-01 22:53:32 +08:00
Sukka c91ad1e016 ci: speed up cargo install by enabling cache (webview2) (#1283) 2024-07-01 14:23:21 +08:00
MystiPanda b1444b8635 feat: global merge and script 2024-07-01 08:25:03 +08:00
MystiPanda 51a49b94d8 feat: drag to reorder rules 2024-06-30 23:16:21 +08:00
MystiPanda 720b46d790 chore: select rule-set name 2024-06-30 22:46:11 +08:00
MystiPanda 9089c30d57 feat: display rules list 2024-06-30 22:34:26 +08:00
MystiPanda e2d522803c fix: editor init error 2024-06-30 18:05:38 +08:00
Sukka babcb00621 ci: speed up cargo install by enabling cache (#1279) 2024-06-30 17:21:55 +08:00
MystiPanda 901a983150 feat: rules editor 2024-06-30 17:17:04 +08:00
MystiPanda 955182b05b chore: locale 2024-06-30 13:03:36 +08:00
MystiPanda f4dfe8eeb4 feat(unfinished): rules editor 2024-06-30 12:46:31 +08:00
MystiPanda 93904b8278 refactor: pure merge 2024-06-30 07:58:44 +08:00
MystiPanda 495580ae2b fix: delete logic 2024-06-30 07:37:52 +08:00
MystiPanda 3051004217 chore: update locale 2024-06-30 07:18:10 +08:00
MystiPanda b854b5e1ac feat: support seq editor 2024-06-30 00:22:05 +08:00
MystiPanda cf61a96ef6 refactor: Associate Profile with Merge/Script. 2024-06-29 23:07:44 +08:00
MystiPanda 1293d25e1b refactor: use async instead of block_on 2024-06-29 19:02:37 +08:00
dongchengjie c1a201f358 fix: do not reactive when changed profile is not current 2024-06-29 11:22:11 +08:00
dongchengjie 9ee5390ec7 feat: reactive after save when profile content changes 2024-06-29 09:21:50 +08:00
dongchengjie 3f1caa702b fix: #1261 2024-06-29 05:02:06 +08:00
wonfen 24f9573c05 chore: add TG link & descriptions 2024-06-29 04:55:43 +08:00
dongchengjie 83b25920ea chore: add descriptions for Miscellaneous 2024-06-28 15:54:27 +08:00
MystiPanda 834edcd03e fix: unset dns when exit 2024-06-28 12:18:18 +08:00
MystiPanda 8f3801e3c2 feat: set dns by service 2024-06-28 11:45:44 +08:00
dongchengjie b5f7c58276 chore: remove duplicate locales 2024-06-26 19:11:39 +08:00
dongchengjie 66ae293ddd fix: await compatibility in #8e78b9e 2024-06-26 18:29:50 +08:00
dongchengjie 8e78b9e405 feat: support manual memory cleanup when running in debug mode 2024-06-26 17:44:42 +08:00
dongchengjie 9da1759247 chore: cleanup 2024-06-26 08:24:43 +08:00
dongchengjie 704c41c0f2 refactor: extract tooltip icon as component 2024-06-26 08:10:18 +08:00
dongchengjie 1f422afe3d chore: tooltips and locales 2024-06-26 05:33:06 +08:00
dongchengjie 709a23cf09 chore: fix connection table bg 2024-06-23 20:40:09 +08:00
dongchengjie bfa3fa293f fix: update_interval won't save when creating local profile while updating does & number input locales 2024-06-23 06:47:51 +08:00
MystiPanda d8b878b1bb feat: handle break change update 2024-06-22 21:05:46 +08:00
dongchengjie b2589dbc04 chore: hotkeys display delimiter 2024-06-21 18:24:58 +08:00
MystiPanda 2a4b605794 chore: hide delay for preset outbound 2024-06-21 00:16:41 +08:00
MystiPanda a3080a3373 revert: just kill 2024-06-20 14:27:13 +08:00
MystiPanda b4b9ae5d7f feat: custom dmg background 2024-06-20 13:50:28 +08:00
dongchengjie bb9a93e462 chore: fix table header bg in connections 2024-06-20 02:01:51 +08:00
dongchengjie e55fbf675e chore: disable shortcuts #1239 2024-06-20 01:39:51 +08:00
MystiPanda 0342477733 fix: runtime error 2024-06-19 11:10:40 +08:00
MystiPanda e6e2b1f142 refactor: change core binary name 2024-06-19 10:04:28 +08:00
MystiPanda 95c23a93cd refactor: kill core by process name 2024-06-18 12:36:58 +08:00
MystiPanda ae784cb985 fix: reg error 2024-06-17 16:38:30 +08:00
Sukka 17765bdba9 fix(#1226): missing conn unsub (#1228) 2024-06-17 13:36:35 +08:00
Sukka 45f791b3c8 refactor(connections): use swr subscription (#1226) 2024-06-17 13:14:36 +08:00
dongchengjie b0ad39ed0b fix: resizing reset when data changes 2024-06-17 13:03:47 +08:00
Sukka 1988aeb945 refactor(log): use swr subscription (#1220) 2024-06-17 10:48:37 +08:00
MystiPanda f48a5710aa chore: update depends 2024-06-17 10:46:18 +08:00
Sukka c8d2410c27 feat: add a wrapper around sockette w/ error retry (#1219)
* feat: add a wrapper around sockette w/ error retry

* chore: use import path alias

* perf: reduce retry
2024-06-16 18:25:33 +08:00
MystiPanda a9ef32c32f Revert "feat: disable running with admin permission and check service mode"
This reverts commit a0f9fb90ee.
2024-06-16 12:06:23 +08:00
Sukka 19b6f78c8a ci(alpha): avoid race by cancel non-latest concurrent runs (#1213) 2024-06-16 01:10:30 +08:00
Sukka 4ab507fd43 fix: hide save button in readonly editor view (#1208) 2024-06-15 19:24:26 +08:00
Sukka 2662df2547 fix(#1203): correct types (#1207) 2024-06-15 19:23:58 +08:00
Sukka 0332415ac9 perf: replace Array#map Array#filter chain w/ Array#reduce (#1203) 2024-06-15 12:22:33 +08:00
MystiPanda a0f9fb90ee feat: disable running with admin permission and check service mode 2024-06-14 23:15:49 +08:00
MystiPanda 1b8c4cb832 build: downgrade auto-launch 2024-06-14 21:26:30 +08:00
MystiPanda cfd50d281b fix: fix bypass check regex 2024-06-14 21:23:58 +08:00
Sukka 1667856894 chore: use swr subscription for layout traffic / memory (#1202)
* chore: update swr to 2

* refactor: use swr subscription for memory & traffic

* refactor: introduce `sockette`
2024-06-14 18:23:29 +08:00
dongchengjie c5c76ab539 chore: update metacubexd #1200 2024-06-14 17:09:12 +08:00
dongchengjie eb060d2e43 feat: local profile name autofill #1191 2024-06-13 16:29:25 +08:00
MystiPanda b5556613cf refactor: remove grant logic 2024-06-13 16:07:56 +08:00
MystiPanda ad1a057edb fix: check service 2024-06-13 12:58:47 +08:00
MystiPanda 8342f985e5 ci: fix release 2024-06-12 12:25:12 +08:00
MystiPanda 9d32e58915 chore: update publisher 2024-06-12 11:51:44 +08:00
MystiPanda 84027cbae1 Release 1.6.6 2024-06-12 10:33:20 +08:00
MystiPanda 5343040ef3 fix: start param error 2024-06-12 10:19:23 +08:00
MystiPanda 75cd7dd4b9 chore: update & fmt & clippy 2024-06-12 10:00:22 +08:00
dongchengjie fb6ffd8e09 chore: missing locale 2024-06-11 16:19:25 +08:00
dongchengjie aca9e61f39 chore: locale "Invalid Bypass Format" 2024-06-09 20:02:20 +08:00
MystiPanda 56078098a4 fix: start param error 2024-06-09 20:00:17 +08:00
MystiPanda 1f197965e0 feat: check bypass format 2024-06-09 13:37:47 +08:00
MystiPanda 183f0f6c42 feat: keep default bypass 2024-06-09 12:45:57 +08:00
dongchengjie 76624c7d83 chore: missing locale 2024-06-09 11:16:13 +08:00
dongchengjie ab47c56ec5 chore: locale reorganization 2024-06-09 09:12:14 +08:00
Eric Huang ad35ed96c8 feat(settings page): add loading state (#1157)
* feat(settings page): add loading state

* fix: type
2024-06-09 06:26:07 +08:00
wonfen 97bd51cf5f chore: update ci with apple notarization 2024-06-09 02:01:33 +08:00
MystiPanda f4a110cfd7 ci: fix error 2024-06-08 20:58:57 +08:00
MystiPanda e9ed06adfd ci: update 2024-06-08 20:45:28 +08:00
MystiPanda bc5eaf34fe feat: add download button on updater dialog
#1129
2024-06-08 20:35:23 +08:00
MystiPanda 3e8b891dd0 fix: run app as normal user 2024-06-08 20:20:47 +08:00
MystiPanda 3f2ea4bc64 build: update depends 2024-06-08 20:02:25 +08:00
MystiPanda ae81b37c1d build: remove appimage 2024-06-08 19:36:58 +08:00
MystiPanda b4349afb1b ci: codesign 2024-06-08 18:52:34 +08:00
dongchengjie 0cffe8e3f7 chore: disbale Meta+Q on macOS 2024-06-08 13:12:24 +08:00
Sukka 7a1f4f9abe refactor: simplify useConnectionSetting (#1141) 2024-06-07 17:23:53 +08:00
wonfen 5768b01786 fix: apple sign error again 2024-06-07 17:19:44 +08:00
MystiPanda 7e9079467f fix: sign error 2024-06-07 16:39:06 +08:00
MystiPanda 615e96922e fix: sign error 2024-06-07 16:37:58 +08:00
Eric Huang 8b94c452fb feat: make SettingItem clickable (#1138)
* feat: make `SettingItem` clickable

* clean up
2024-06-07 15:51:51 +08:00
Sukka 66dd510acc refactor: replace recoil (#1137) 2024-06-07 12:27:37 +08:00
wonfen 96e044566c Style: modify proxy pages 2024-06-07 10:32:27 +08:00
MystiPanda 726ad84d7f fix: sign error 2024-06-06 16:06:58 +08:00
wonfen 4fc422f880 chore: apple developer sign test 2024-06-06 15:21:53 +08:00
dongchengjie 1938024ab3 chore: test_delay trace 2024-06-05 19:17:43 +08:00
dongchengjie 05b1de6fd5 chore: theme colors to uppercase 2024-06-05 14:20:24 +08:00
dongchengjie 0bf8807b50 build: no legacy chunks 2024-06-05 09:04:08 +08:00
Sukka 23668ee74e fix(#1126): add Object.hasOwn polyfill (#1127) 2024-06-05 00:12:06 +08:00
dongchengjie 6bc5c5a808 fix: csp missing asset: 2024-06-04 15:26:39 +08:00
MystiPanda 330004dc72 build: update depends 2024-06-04 12:16:42 +08:00
MystiPanda a900d6d742 Release 1.6.5 2024-06-04 12:11:38 +08:00
MystiPanda bfea70eec9 fix: package error 2024-06-04 10:47:17 +08:00
MystiPanda 13babbf330 feat: support rpm package 2024-06-04 10:16:25 +08:00
MystiPanda 0a6fe382ac build: use latest tauri 2024-06-04 10:09:04 +08:00
Zhenfu Shi dd6dea9dfc chore: Enable Ad Hoc signing on macOS (#1117)
Closes #1116
2024-06-03 09:01:33 +08:00
wonfen 11434fba68 Revert "chore: change default test url"
This reverts commit 69b65d06a4.
2024-06-03 08:25:06 +08:00
dongchengjie 10bf90c92a chore: use icons files instead of hard coding 2024-06-03 07:21:40 +08:00
dongchengjie 5193fe4bab fix: group selected be overwritten when saving test 2024-06-03 06:38:20 +08:00
dongchengjie 4df4995623 chore: improve UI in connections page 2024-06-02 21:52:31 +08:00
aixiao0621 f5f4ecf46a fix: a display error on the connections page 2024-06-02 17:26:23 +08:00
MystiPanda 69b65d06a4 chore: change default test url 2024-06-01 23:14:47 +08:00
MystiPanda e6bf4e836d build: update depends 2024-06-01 23:01:27 +08:00
dongchengjie 01d67eb239 refactor: polyfills review 2024-05-30 20:27:12 +08:00
dongchengjie 3514cfbd44 fix: research when search box mode changes 2024-05-30 10:45:24 +08:00
dongchengjie cade35fe10 chore: Content-Security-Policy 2024-05-29 10:20:56 +08:00
dongchengjie 2c6df77f5a ci: fix heap oom 2024-05-29 09:49:11 +08:00
dongchengjie 0276ae91b5 build: polyfills 2024-05-29 09:39:26 +08:00
dongchengjie 60fee2accb build: import babel 2024-05-28 09:59:41 +08:00
dongchengjie 03e2632294 fix: try to fix #1084 2024-05-27 23:19:44 +08:00
dongchengjie 194fe59458 typo: inconsistent style in layout-viewer 2024-05-27 16:37:48 +08:00
MystiPanda e400111f8a Release 1.6.4 2024-05-26 22:07:53 +08:00
MystiPanda 8bc7607783 Revert "refactor: use axios tauri adapter"
This reverts commit 7e361cb3f1.
2024-05-26 21:18:02 +08:00
dongchengjie 4108451ebe chore: %mixed-port% hint for PAC script 2024-05-26 19:53:42 +08:00
MystiPanda 0f56257aa1 Release 1.6.3 2024-05-26 19:43:26 +08:00
MystiPanda 2a575284b9 chore: add content type 2024-05-26 19:26:57 +08:00
MystiPanda 444bcbfb30 fix: auto proxy changed by guard 2024-05-26 19:07:14 +08:00
MystiPanda 212021c878 feat: Support PAC Mode 2024-05-26 17:59:39 +08:00
dongchengjie 600b687253 chore: emoji display support in editor 2024-05-25 18:02:32 +08:00
farzadhallaji ae3aada172 Update fa.json (#1060) 2024-05-24 19:39:40 +08:00
MystiPanda 50cc8af8c3 ci: use rust 1.77.0 2024-05-23 11:47:41 +08:00
dongchengjie 1809cdbf37 fix: no snippets and warnings in runtime config editor 2024-05-21 06:18:09 +08:00
dongchengjie fb38047769 chore: update locale in connection 2024-05-20 13:42:45 +08:00
dongchengjie 08e0d6a34a chore: text overflow word-wrap & cleanup 2024-05-20 12:38:41 +08:00
MystiPanda b4fddaec7f build: use compatible 2024-05-19 19:30:53 +08:00
MystiPanda 3f19d22941 fix: installer package error 2024-05-19 19:13:21 +08:00
MystiPanda c7d51cfc4c build: use latest core 2024-05-19 13:00:27 +08:00
dongchengjie 6829d34e90 chore: schema 1.18.5-alpha 2024-05-19 12:37:18 +08:00
dongchengjie 021e430103 chore: fix select component bg in connection 2024-05-18 18:42:51 +08:00
dongchengjie ae6f42a7fb chore: fix search-box bg 2024-05-18 18:20:05 +08:00
MystiPanda da98097394 chore: support more asset scope 2024-05-18 15:47:45 +08:00
oomeow 43f0b935cf perf: memoize the proxy col items (#1029) 2024-05-18 15:14:22 +08:00
oomeow db5d14e0ce fix: clipboard doesn't work and set_shadow method is not supported in Linux (#1030) 2024-05-18 15:14:00 +08:00
dongchengjie b25cf5eadb feat: editor import PAC definition 2024-05-18 14:59:17 +08:00
dongchengjie bd3b41c809 chore: hint for canceling fixed #840 2024-05-17 20:44:18 +08:00
dongchengjie 31d301064a chore: use search-box in logs and connections 2024-05-17 19:44:42 +08:00
dongchengjie b95b9bdaf3 chore: component base-search-box 2024-05-17 19:13:33 +08:00
dongchengjie 937f0b5687 chore: fix editor dialog scroll overflow 2024-05-17 04:03:49 +08:00
dongchengjie ffbc892e44 fix: missing locale 2024-05-15 23:27:31 +08:00
dongchengjie 9e872932d1 chore: current bypass wrap 2024-05-15 22:59:53 +08:00
MystiPanda ecb2fbf900 feat: support monochrome tray icon 2024-05-15 14:50:10 +08:00
dongchengjie 72edd2e15d chore: logo svg 2024-05-14 16:53:39 +08:00
dongchengjie c3d22c554f feat: doc reference link on settings header 2024-05-14 14:40:47 +08:00
dongchengjie e72f4a1582 chore: use coding fonts in editor 2024-05-14 14:20:50 +08:00
dongchengjie 5e43c060fa fix: switch missing break 2024-05-14 01:24:23 +08:00
dongchengjie e8dee1ddd9 chore: @ts-ignore schema check 2024-05-14 01:21:28 +08:00
dongchengjie d85b8c776f chore: revert schema to beta4 2024-05-14 00:59:05 +08:00
dongchengjie fb518b6218 chore: palette locale 2024-05-13 23:12:29 +08:00
dongchengjie 8d510cde21 typo: unused import 2024-05-13 22:59:24 +08:00
dongchengjie da4bf167ee feat: css injection editor 2024-05-13 22:58:25 +08:00
Remember dc1abf8dcb Update 172.16.0.0/12 on Windows (#1013) 2024-05-13 18:28:41 +08:00
MystiPanda 3fc969a50b fix: use default bypass when empty 2024-05-13 18:27:19 +08:00
MystiPanda 4064129d49 chore: Adapt Mac icon 2024-05-13 13:11:05 +08:00
MystiPanda 22b4e0bfff docs: Update README 2024-05-12 22:17:05 +08:00
MystiPanda 53fa1f88fd chore: Change HomePage Logo 2024-05-12 21:17:21 +08:00
MystiPanda 5fefcd92f3 chore: Try a new icon 2024-05-12 19:31:06 +08:00
dongchengjie d20bc936da chore: ru locale 2024-05-12 16:18:49 +08:00
RikudouPatrickstar e79f036a70 chore: update notification message and zh translation (#1011) 2024-05-12 14:40:18 +08:00
MystiPanda 0f2af91a04 perf: Disable Tun mode before shutting down 2024-05-12 11:06:44 +08:00
MystiPanda 7e361cb3f1 refactor: use axios tauri adapter 2024-05-11 21:54:56 +08:00
MystiPanda 1f7acd4027 feat: Allow disable unused ports 2024-05-09 09:22:32 +08:00
MystiPanda dddca7bf4b chore: Update locale 2024-05-08 23:45:55 +08:00
MystiPanda e520be80c0 perf: Limit drawing frame rate 2024-05-08 22:33:13 +08:00
MystiPanda 0b78dedf04 fix: bundle error 2024-05-08 14:52:38 +08:00
MystiPanda 8f409477c8 build: fix bundle error 2024-05-08 14:20:30 +08:00
MystiPanda 9c22f7ce69 fix: depend error 2024-05-08 13:24:34 +08:00
MystiPanda 05fa916915 Release 1.6.2 2024-05-08 13:14:00 +08:00
MystiPanda 9a593d7a61 feat: support upgrade for release core 2024-05-08 12:48:27 +08:00
MystiPanda 30017eeaa0 docs: Update FAQ URL 2024-05-08 11:44:57 +08:00
MystiPanda 9b03cacc94 ci: update ci 2024-05-08 11:12:06 +08:00
MystiPanda 557970cf54 refactor: remove prepend and append for provider 2024-05-08 00:22:14 +08:00
MystiPanda d90dac60ef feat: deep merge
#983
2024-05-08 00:13:32 +08:00
MystiPanda b4b2e67260 build: Add portable for fixed webview2 2024-05-06 19:13:08 +08:00
MystiPanda bcd5e935e7 ci: debug ci 2024-05-06 16:16:47 +08:00
MystiPanda b322627609 ci: fix ci script 2024-05-06 15:30:00 +08:00
MystiPanda 09861095ff ci: update release body 2024-05-06 15:15:07 +08:00
MystiPanda 47e567fb40 docs: update url 2024-05-06 15:05:56 +08:00
MystiPanda 5bb30ad28f build: try support fixed webview2 runtime 2024-05-06 14:18:12 +08:00
MystiPanda a46bbf05ec ci: try resupport x86 2024-05-06 13:04:29 +08:00
dongchengjie 04a4705dbd fix: non-ascii character secret causes controller link error
https://github.com/clash-verge-rev/clash-verge-rev/issues/973#issuecomment-2094839700
2024-05-05 23:04:33 +08:00
MystiPanda 3271c96531 fix: enhance when change tun config 2024-05-04 16:12:10 +08:00
dongchengjie 05fa6b9aba chore: profile template typo 2024-05-04 14:24:11 +08:00
MystiPanda 7edd7f27cb fix: try to set env 2024-05-03 18:00:55 +08:00
dongchengjie 4a6b12eda1 fix: #974 2024-05-03 12:50:04 +08:00
MystiPanda a354e1a0eb chore: update locale 2024-05-02 23:44:09 +08:00
dongchengjie c2c419522a chore: add service mode locale 2024-05-02 22:16:05 +08:00
MystiPanda 7a3cc7d242 feat: Support drag and drop local files 2024-05-02 20:41:43 +08:00
MystiPanda bd6f02f6af fix: change script execution path 2024-05-01 11:39:09 +08:00
MystiPanda bd33728fd7 Release 1.6.1 2024-04-30 23:43:51 +08:00
MystiPanda 8400ee8a3d fix: MacOS tray click 2024-04-30 23:21:25 +08:00
lxk955 80ee7aef8e feat: Support for using regular expressions on the log page #707 (#959) 2024-04-30 22:59:42 +08:00
MystiPanda 41762be3a5 fix: service install path 2024-04-30 20:20:39 +08:00
MystiPanda 605dda7b76 build: Restore core version 2024-04-30 09:42:25 +08:00
dongchengjie 8d6e3b5a58 fixup: wrong version 2024-04-30 02:13:24 +08:00
dongchengjie 53920fc368 chore: update pnpm-lock.yaml (#952)
* chore: update schema version

* Update pnpm-lock.yaml
2024-04-30 01:44:05 +08:00
dongchengjie 96d10423d9 chore: update schema version (#950) 2024-04-29 22:46:29 +08:00
MystiPanda 42be9b5b51 chore: Config issue template 2024-04-29 15:43:43 +08:00
MystiPanda 0add041516 docs: Update FAQ URL 2024-04-28 20:04:03 +08:00
MystiPanda dee76cac8d build: cargo update 2024-04-28 19:59:46 +08:00
MystiPanda 9646fab22c fix: Open the link with browser 2024-04-28 18:46:49 +08:00
dongchengjie a54e9cb244 fixup: can't edit file (#943) 2024-04-28 17:00:33 +08:00
dongchengjie f8339c7a9a chore: disable WebView keyboard shortcuts (#942) 2024-04-28 16:19:13 +08:00
lxk955 9e2c864117 feat: support display current profile when the mouse is placed on the tray icon #782 (#938) 2024-04-27 20:54:02 +08:00
PlayerNeo e1ccd71455 fix: disable left click menu on macOS (#930) 2024-04-27 20:52:29 +08:00
LiuTianYuandtyliu9 0e82426ea1 fix: #730 icon not change when the window is opened maximized (#924)
Co-authored-by: tyliu9 <tyliu9@toycloud.com>
2024-04-24 22:36:15 +08:00
wonfen 6c7afd168a Release 1.6.0 2024-04-23 14:25:09 +08:00
MystiPanda e014fdf3da feat: support ico format for tray icon (#911) 2024-04-22 20:43:15 +08:00
dongchengjie 2074da05c8 fix: #907 (#908) 2024-04-22 01:40:25 +08:00
dongchengjie 84cd87b70a chore: update locale (#904)
* chore: missing locale

* chore: External Controller locale
2024-04-21 14:10:34 +08:00
dongchengjie 75f87044f6 chore: Proxy Bypass placeholder (#901) 2024-04-20 19:27:17 +08:00
dongchengjie f71b51e64a fix: minor glitches (#900)
* feat: show actual proxy name instead of proxy group when hovering on a group outbound

* fix: open empty edit form and save will cause `UID not found`

* chore: tauri.conf.json json schema

* chore: missing locales
2024-04-20 18:02:15 +08:00
MystiPanda 9d741cdd63 fix: startup script blocking 2024-04-20 17:52:54 +08:00
MystiPanda 56be17000a fix: default value for tun 2024-04-20 17:42:36 +08:00
MystiPanda f6aacbc31d build: add depends 2024-04-19 15:55:46 +08:00
MystiPanda ad4b57c327 chore: update lock file 2024-04-19 15:38:32 +08:00
dongchengjie 3794b2f0de fix: 使用npm安装meta-json-schema (#895)
* feat: allow manual selection of url-test group

* feat: fixed proxy indicator

* fix: try to fix traffic websocket no longer updating

* fixup: group delay test use defined url

* feat: connections sorted by start by default

* feat: Connection details show the full path of the process

* fix: editor no hints and add yaml support

* feat: quick suggestions

* chore: use monaco-editor

* chore: update schema url

* chore: change default merge config content

* fix: load schema via npm

* feat: runtime config viewer style auto adjust

* feat: adjust fixed proxy style

* fix: headState "showType" won't toggle hover text

* chore: switch version

* chore: Update pnpm lockfile
2024-04-19 13:54:16 +08:00
MystiPanda e0e8412728 refactor: change default value of mtu 2024-04-18 10:27:09 +08:00
dongchengjie 05b8175aad chore: 修改Merge配置文件默认内容 (#889) 2024-04-17 22:56:54 +08:00
MystiPanda 339c89dd1c chore: Update pnpm lockfile 2024-04-17 22:16:50 +08:00
dongchengjie 402299e9c8 feat: Clash配置、Merge配置提供JSON Schema语法支持、[连接]界面调整 (#887) 2024-04-17 21:19:37 +08:00
MystiPanda d0e8f450fd fix: SymbolicLink
#750
2024-04-13 15:46:11 +08:00
MystiPanda e97b1ccd7a fix: service install path 2024-04-13 15:12:41 +08:00
汐殇 68691b91ef fix: Allow core files in specific directories to be upgraded normally (#857) 2024-04-12 01:16:57 +08:00
dongchengjie 213d417481 feat: url-test支持手动选择、节点组fixed节点使用角标展示 (#840)
* feat: allow manual selection of url-test group

* feat: fixed proxy indicator

* fix: try to fix traffic websocket no longer updating

* fixup: group delay test use defined url
2024-04-09 13:15:45 +08:00
MystiPanda 2ec841eb61 fix: Update the home while updating profile 2024-04-04 15:07:55 +08:00
MystiPanda 60b5c54be1 fix: Use System Browser 2024-04-04 14:56:10 +08:00
Damian Johnson 448412a191 style: adjust confirm dialog & web ui settings dialog (#821) 2024-04-03 23:19:00 +08:00
Damian Johnson e06327936e fix: service mode install script download not found (#822) 2024-04-03 23:18:10 +08:00
dongchengjie 5f044e1aed feat: support URL Schema 'profile-web-page-url' (#816) 2024-04-01 19:28:28 +08:00
HZ is not Chatty d90fb6fc9b fixup! feat: Service Mode for Linux (#804) (#815)
* fixup! feat: Service Mode for Linux (#804)

* fixup! feat: Service Mode for Linux (#804)

* Partially revert "fixup! feat: Service Mode for Linux (#804)"

This reverts commit e6a5a2b496.
2024-04-01 19:25:58 +08:00
MystiPanda 1c583d2ea9 feat: Try support service mode for MacOS 2024-03-31 23:16:47 +08:00
MystiPanda ddab131102 fix: script error 2024-03-31 16:37:33 +08:00
HZ is not Chatty 3b06cf8a2a feat: Service Mode for Linux (#804) 2024-03-31 16:16:23 +08:00
wonfen c5d7c29f3d Sytle: fix mac logo padding 2024-03-30 16:31:17 +08:00
Damian Johnson 26af860eac fix: icon not change when toggle window maxinized (#799) 2024-03-30 01:16:40 +08:00
cismous ec9852eb98 Style filter input (#724)
* refactor: reduce duplicate code

* style: add a white background to the light color theme to avoid the gray text being too light
2024-03-30 01:14:03 +08:00
Damian Johnson f2b8c6d0ca fix: missing proxy group delay check animations (#788)
* fix: missing proxy group delay check animation

* chore: cleanup

* chore: adjust content style
2024-03-29 08:20:31 +08:00
wonfen 2cf7a048cf Release 1.5.11 2024-03-28 12:06:41 +08:00
MystiPanda 3dbb71a076 feat: Support Persian
#715
2024-03-21 20:51:33 +08:00
MystiPanda ddf6760543 fix: Duplicate icon display error
#719
2024-03-21 20:29:16 +08:00
MystiPanda a6fa323f33 fix: The update button cannot be clicked
#716
2024-03-21 19:56:30 +08:00
MystiPanda 2df8f1acdb fix: Check if the install directory is empty when uninstall 2024-03-21 15:24:12 +08:00
MystiPanda 74a3290abc chore: fix typo 2024-03-21 14:09:59 +08:00
286 changed files with 35433 additions and 10715 deletions
+5
View File
@@ -0,0 +1,5 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
+22 -4
View File
@@ -2,19 +2,20 @@ name: 问题反馈 / Bug report
title: "[BUG] "
description: 反馈你遇到的问题 / Report the issue you are experiencing
labels: ["bug"]
type: "Bug"
body:
- type: markdown
attributes:
value: |
## 在提交问题之前,请确认以下事项:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq.html)
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填写一个简洁明了的标题,以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) and [FAQ](https://clash-verge-rev.github.io/faq.html)
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) and [FAQ](https://clash-verge-rev.github.io/faq/windows.html)
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the problem still exists
@@ -24,7 +25,13 @@ body:
id: description
attributes:
label: 问题描述 / Describe the bug
description: 详细清晰地描述你遇到的问题 / A clear and concise description of what the bug is
description: 详细清晰地描述你遇到的问题,并配合截图 / Describe the problem you encountered in detail and clearly, and provide screenshots
validations:
required: true
- type: textarea
attributes:
label: 软件版本 / Verge Version
description: 请提供Verge的具体版本,如果是alpha版本,请注明下载时间(精确到小时分钟) / Please provide the specific version of Verge. If it is an alpha version, please indicate the download time (accurate to hours and minutes)
validations:
required: true
- type: textarea
@@ -48,9 +55,20 @@ body:
description: 请提供你的操作系统版本,Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
validations:
required: true
- type: checkboxes
id: os-labels
attributes:
label: 系统标签 / OS Labels
description: 请选择受影响的操作系统(至少选择一个) / Please select the affected operating system(s) (select at least one)
options:
- label: windows
- label: macos
- label: linux
validations:
required: true
- type: textarea
attributes:
label: 日志 / Log
description: 请提供完整或相关部分的Debug日志 / Please provide the complete or relevant part of the Debug log
description: 请提供完整或相关部分的Debug日志(请在“软件左侧菜单”->“设置”->“日志等级”调整到debug,Verge错误请把“杂项设置”->“app日志等级”调整到debug/trace,并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to trace, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
validations:
required: true
+4
View File
@@ -0,0 +1,4 @@
contact_links:
- name: 讨论交流 / Communication
url: https://t.me/clash_verge_rev
about: 在 Telegram 群组中与其他用户讨论交流 / Communicate with other users in the Telegram group
+14 -2
View File
@@ -2,19 +2,20 @@ name: 功能请求 / Feature request
title: "[Feature] "
description: 提出你的功能请求 / Propose your feature request
labels: ["enhancement"]
type: "Feature"
body:
- type: markdown
attributes:
value: |
## 在提交问题之前,请确认以下事项:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 确认软件不存在类似的功能
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下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) to confirm that the software does not have similar functions
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) to confirm that the software does not have similar functions
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
@@ -33,3 +34,14 @@ body:
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
validations:
required: true
- type: checkboxes
id: os-labels
attributes:
label: 适用系统 / Target OS
description: 请选择该功能适用的操作系统(至少选择一个) / Please select the operating system(s) for this feature request (select at least one)
options:
- label: windows
- label: macos
- label: linux
validations:
required: true
-4
View File
@@ -1,4 +0,0 @@
FROM rust:buster
COPY entrypoint.sh /entrypoint.sh
RUN chmod a+x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
-14
View File
@@ -1,14 +0,0 @@
name: "Build for Linux"
branding:
icon: user-check
color: gray-dark
inputs:
target:
required: true
description: "Rust Target"
runs:
using: "docker"
image: "Dockerfile"
args:
- ${{ inputs.target }}
-8
View File
@@ -1,8 +0,0 @@
pnpm install
pnpm check $INPUT_TARGET
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
pnpm build --target $INPUT_TARGET
else
pnpm build --target $INPUT_TARGET -b deb
fi
-47
View File
@@ -1,47 +0,0 @@
#!/bin/bash
wget https://nodejs.org/dist/v20.10.0/node-v20.10.0-linux-x64.tar.xz
tar -Jxvf ./node-v20.10.0-linux-x64.tar.xz
export PATH=$(pwd)/node-v20.10.0-linux-x64/bin:$PATH
npm install pnpm -g
rustup target add "$INPUT_TARGET"
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
apt-get update
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
dpkg --add-architecture arm64
apt-get update
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_ALLOW_CROSS=1
elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
dpkg --add-architecture armhf
apt-get update
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
export PKG_CONFIG_ALLOW_CROSS=1
elif [ "$INPUT_TARGET" = "riscv64gc-unknown-linux-gnu" ]; then
dpkg --add-architecture riscv64
apt-get update
apt-get install -y libncurses6:riscv64 libtinfo6:riscv64 linux-libc-dev:riscv64 libncursesw6:riscv64 libssl3:riscv64 libcups2:riscv64
apt-get install -y --no-install-recommends g++-riscv64-linux-gnu libc6-dev-riscv64-cross libwebkit2gtk-4.0-dev:riscv64 libgtk-3-dev:riscv64 patchelf:riscv64 librsvg2-dev:riscv64 libayatana-appindicator3-dev:riscv64
export CARGO_TARGET_RISCV64_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
export CC_riscv64_unknown_linux_gnu=riscv64-linux-gnu-gcc
export CXX_riscv64_unknown_linux_gnu=riscv64-linux-gnu-g++
export PKG_CONFIG_PATH=/usr/lib/riscv64-linux-gnu/pkgconfig
export PKG_CONFIG_ALLOW_CROSS=1
else
echo "Unknown target: $INPUT_TARGET" && exit 1
fi
bash .github/build-for-linux/build.sh
+280 -73
View File
@@ -2,16 +2,53 @@ name: Alpha Build
on:
workflow_dispatch:
push:
branches: [main]
tags-ignore: [updater, alpha]
schedule:
# UTC+8 00:00 (UTC 16:00 previous day) and UTC+8 12:00 (UTC 04:00)
- cron: "0 16,4 * * *"
permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
concurrency:
# only allow per workflow per commit (and not pr) to run at a time
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check_commit:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check if commit changed
id: check
run: |
# For manual workflow_dispatch, always run
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check if current commit is different from the previous one
CURRENT_COMMIT=$(git rev-parse HEAD)
PREVIOUS_COMMIT=$(git rev-parse HEAD~1)
if [ "$CURRENT_COMMIT" != "$PREVIOUS_COMMIT" ]; then
echo "New commit detected: $CURRENT_COMMIT"
echo "should_run=true" >> $GITHUB_OUTPUT
else
echo "No new commits since last run"
echo "should_run=false" >> $GITHUB_OUTPUT
fi
alpha:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
@@ -24,6 +61,8 @@ jobs:
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
@@ -40,16 +79,23 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22"
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 8
run_install: false
- name: Pnpm install and check
@@ -57,12 +103,22 @@ jobs:
pnpm i
pnpm check ${{ matrix.target }}
- name: Alpha Version update
run: pnpm run fix-alpha-version
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: alpha
releaseName: "Clash Verge Rev Alpha"
@@ -72,37 +128,109 @@ jobs:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Portable Bundle
if: matrix.os == 'windows-latest'
run: pnpm portable ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
alpha-for-linux:
alpha-for-linux-arm:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
- os: ubuntu-latest
arch: arm64
- os: ubuntu-22.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Build for Linux
uses: ./.github/build-for-linux
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
target: ${{ matrix.target }}
workspaces: src-tauri
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
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
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
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 update
sudo apt install -y \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
- name: "Install aarch64 tools"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu
- name: "Install armv7 tools"
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
fi
pnpm build --target ${{ matrix.target }}
env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Get Version
run: |
@@ -111,52 +239,120 @@ jobs:
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- run: |
cat > release.txt << 'EOF'
### 我应该下载哪个版本?
- Windows x86_64架构: x64-setup.exe (不支持win7)
- Windows arm64架构: arm64-setup.exe
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
- Linux x64架构: amd64.AppImage/amd64.deb
- Linux arm64架构: arm64.deb
- Linux armv7架构: armhf.deb
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
### FAQ
- [https://clash-verge-rev.github.io/faq.html](https://clash-verge-rev.github.io/faq.html)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
if: startsWith(matrix.target, 'x86_64')
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-for-fixed-webview2:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- 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
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\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"
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"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}
- name: Upload Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: [alpha, alpha-for-linux]
needs: [check_commit, alpha, alpha-for-linux-arm, alpha-for-fixed-webview2]
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -166,36 +362,47 @@ jobs:
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Update Tag
uses: richardsimko/update-tag@v1
with:
tag_name: alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - name: Update Tag
# uses: richardsimko/update-tag@v1
# with:
# tag_name: alpha
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
### 我应该下载哪个版本?
## 我应该下载哪个版本?
- Windows x86_64架构: x64-setup.exe (不支持win7)
- Windows arm64架构: arm64-setup.exe
### MacOS
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
- Linux x64架构: amd64.AppImage/amd64.deb
- Linux arm64架构: arm64.deb
- Linux armv7架构: armhf.deb
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
- MacOS apple M芯片: aarch64.dmg
### Linux
- Linux 64位: amd64.deb/amd64.rpm
- Linux arm64 architecture: arm64.deb/aarch64.rpm
- Linux armv7架构: armhf.deb/armhfp.rpm
### Windows (不再支持Win7)
#### 正常版本(推荐)
- 64位: x64-setup.exe
- arm64架构: arm64-setup.exe
#### 便携版问题很多不再提供
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
- 64位: x64_fixed_webview2-setup.exe
- arm64架构: arm64_fixed_webview2-setup.exe
### FAQ
- [https://clash-verge-rev.github.io/faq.html](https://clash-verge-rev.github.io/faq.html)
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
+94
View File
@@ -0,0 +1,94 @@
name: Development Test
on:
workflow_dispatch:
permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
concurrency:
# only allow per workflow per commit (and not pr) to run at a time
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
dev:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
bundle: nsis
- os: macos-latest
target: aarch64-apple-darwin
bundle: dmg
- os: macos-latest
target: x86_64-apple-darwin
bundle: dmg
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }} -b ${{ matrix.bundle }}
- name: Upload Artifacts
if: matrix.os == 'macos-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
if-no-files-found: error
- name: Upload Artifacts
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe
if-no-files-found: error
+260 -39
View File
@@ -6,6 +6,10 @@ permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
concurrency:
# only allow per workflow per commit (and not pr) to run at a time
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
release:
@@ -21,6 +25,8 @@ jobs:
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
@@ -37,16 +43,23 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22"
- uses: pnpm/action-setup@v3
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 8
run_install: false
- name: Pnpm install and check
@@ -57,91 +70,252 @@ jobs:
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: v__VERSION__
releaseName: "Clash Verge Rev v__VERSION__"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: false
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Portable Bundle
if: matrix.os == 'windows-latest'
run: pnpm portable ${{ matrix.target }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
release-for-linux:
release-for-linux-arm:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
- os: ubuntu-latest
arch: arm64
- os: ubuntu-22.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Build for Linux
uses: ./.github/build-for-linux
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
target: ${{ matrix.target }}
workspaces: src-tauri
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
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
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
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 update
sudo apt install -y \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
- name: "Install aarch64 tools"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu
- name: "Install armv7 tools"
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
fi
pnpm build --target ${{ matrix.target }}
env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- name: Upload Release
if: startsWith(matrix.target, 'x86_64')
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: v${{env.VERSION}}
name: "Clash Verge Rev v${{env.VERSION}}"
body: "More new features are now supported."
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
release-for-fixed-webview2:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- 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
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\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"
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"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}
- name: Upload Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: v${{env.VERSION}}
name: "Clash Verge Rev v${{env.VERSION}}"
tag_name: v${{steps.build.outputs.appVersion}}
name: "Clash Verge Rev v${{steps.build.outputs.appVersion}}"
body: "More new features are now supported."
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update:
runs-on: ubuntu-latest
needs: [release, release-for-linux]
needs: [release, release-for-linux-arm]
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22"
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 8
run_install: false
- name: Pnpm install
@@ -151,3 +325,50 @@ jobs:
run: pnpm updater
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update-for-fixed-webview2:
runs-on: ubuntu-latest
needs: [release-for-fixed-webview2]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install
run: pnpm i
- name: Release updater file
run: pnpm updater-fixed-webview2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
submit-to-winget:
runs-on: ubuntu-latest
needs: [release-update]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
- name: Submit to Winget
uses: vedantmgoyal9/winget-releaser@main
with:
identifier: ClashVergeRev.ClashVergeRev
version: ${{env.VERSION}}
release-tag: v${{env.VERSION}}
installers-regex: '_(arm64|x64|x86)-setup\.exe$'
token: ${{ secrets.WINGET_TOKEN }}
+28 -5
View File
@@ -7,17 +7,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "22"
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 8
run_install: false
- name: Pnpm install
@@ -27,3 +26,27 @@ jobs:
run: pnpm updater
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update-for-fixed-webview2:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install
run: pnpm i
- name: Release updater file
run: pnpm updater-fixed-webview2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -1
View File
@@ -1,4 +1,5 @@
node_modules
.pnpm-store
.DS_Store
dist
dist-ssr
@@ -6,4 +7,5 @@ dist-ssr
update.json
scripts/_env.sh
.vscode
.tool-version
.tool-versions
.idea
-3
View File
@@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm pretty-quick --staged
+46 -7
View File
@@ -17,15 +17,28 @@ If you're a Windows user, you may need to perform some additional steps:
- 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
### Install Node.js Packages
After installing Rust and Node.js, install the necessary Node.js packages:
When you setup `Rust` environment, Only use toolchain with `Windows MSVC` , to change settings follow command:
```shell
pnpm i
rustup target add x86_64-pc-windows-msvc
rustup set default-host x86_64-pc-windows-msvc
```
### Download the Clash Binary
### Install Node.js Package
After installing Rust and Node.js, install the necessary Node.js and Node Package Manager:
```shell
npm install pnpm -g
```
### Install Dependencies
```shell
pnpm install
```
### Download the Mihomo Core Binary
You have two options for downloading the clash binary:
@@ -35,7 +48,7 @@ You have two options for downloading the clash binary:
# Use '--force' to force update to the latest version
# pnpm run check --force
```
- Manually download it from the [Clash Meta release](https://github.com/MetaCubeX/Clash.Meta/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
- 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).
### Run the Development Server
@@ -49,12 +62,38 @@ pnpm dev:diff
### Build the Project
If you want to build the project, use:
To build this project:
```shell
pnpm build
```
For a faster build, use the following command
```shell
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.
The `Artifacts` will display in the `log` in the Terminal.
### Build clean
To clean rust build:
```shell
pnpm clean
```
### Portable Version (Windows Only)
To package portable version after the build:
```shell
pnpm portable
```
## Contributing Your Changes
Once you have made your changes:
+20 -12
View File
@@ -1,5 +1,5 @@
<h1 align="center">
<img src="./src/assets/image/logo.png" alt="Clash" width="128" />
<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>
@@ -11,7 +11,9 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
## Preview
![preview](./docs/preview.png)
| Dark | Light |
| -------------------------------- | --------------------------------- |
| ![预览](./docs/preview_dark.png) | ![预览](./docs/preview_light.png) |
## Install
@@ -23,33 +25,39 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
---
### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
### TG 频道: [@clash_verge_rev](https://t.me/clash_verge_re)
## Promotion
[狗狗加速 —— 技术流机场 Doggygo VPN](https://狗狗加速.com)
[狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:https://verge.狗狗加速.com/#/register?code=oaxsAGo6
- 使用 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://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
## Features
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
- Improved UI and supports custom theme color.
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
- System proxy setting and guard.
- 基于性能强劲的 Rust 和 Tauri 2 框架
- 内置[Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo)内核,并支持切换 `Alpha` 版本内核。
- 简洁美观的用户界面,支持自定义主题颜色、代理组/托盘图标以及 `CSS Injection`。
- 配置文件管理和增强(Merge 和 Script),配置文件语法提示。
- 系统代理和守卫、`TUN(虚拟网卡)` 模式。
- 可视化节点和规则编辑
- WebDav 配置备份和同步
### FAQ
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq.html)
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/windows.html)
### Donation
[捐助Clash Verge Rev的开发](https://github.com/sponsors/clash-verge-rev)
## Development
+582 -1
View File
@@ -1,4 +1,585 @@
## 1.5.10
## v2.1.2
**发行代号:臻**
代号释义: 千锤百炼臻至善,集性能跃升、功能拓展、交互焕新于一体,彰显持续打磨、全方位优化的迭代精神。
感谢 Tychristine 对社区群组管理做出的重大贡献!
##### 2.1.2相对2.1.1(已下架不在提供)更新了:
- 无法更新和签名验证失败的问题(该死的CDN缓存)
- 设置菜单区分Verge基本设置和高级设置
- 增加v2 Updater的更多功能和权限
- 退出Verge后Tun代理状态仍保留的问题
##### 2.1.1相对2.1.0(已下架不在提供)更新了:
- 检测所需的Clash Verge Service版本(杀毒软件误报可能与此有关,因为检测和安装新版本Service需管理员权限)
- MacOS下支持彩色托盘图标和更好速率显示(感谢Tunglies)
- 文件类型判断不准导致脚本检测报错的问题
- 打开Win下的阴影(Win10因底层兼容性问题,可能圆角和边框显示不太完美)
- 边框去白边
- 修复Linux下编译问题
- 修复热键无法关闭面板的问题
##### 2.1.0 - 发行代号:臻
### 功能新增
- 新增窗口状态实时监控与自动保存功能
- 增强核心配置变更时的验证与错误处理机制
- 支持通过环境变量`CLASH_VERGE_REV_IP`自定义复制IP地址
- 添加连接表列宽持久化设置与进程过滤功能
- 新增代理组首字母导航与动态滚动定位功能
- 实现连接追踪暂停/恢复功能
- 支持从托盘菜单快速切换代理配置
- 添加轻量级模式开关选项
- 允许用户自定义TUN模式增强类型和FakeIP范围
- 新增系统代理状态指示器
- 增加Alpha版本自动重命名逻辑
- 优化字母导航工具提示与防抖交互机制
### 性能优化
- 重构代理列表渲染逻辑,提升布局计算效率
- 优化代理数据更新机制,采用乐观UI策略
- 改进虚拟列表渲染性能(Virtuoso)
- 提升主窗口Clash模式切换速度(感谢Tunglies)
- 加速内核关闭流程并优化管理逻辑
- 优化节点延迟刷新速率
- 改进托盘网速显示更新逻辑
- 提升配置验证错误信息的可读性
- 重构服务架构,优化代码组织结构(感谢Tunglies)
- 优化内核启动时的配置验证流程
### 问题修复
- 修复删除节点时关联组信息残留问题
- 解决菜单切换异常与重复勾选问题
- 修正连接页流量计算错误
- 修复Windows圆角显示异常问题
- 解决控制台废弃API警告
- 修复全局热键空值导致的崩溃
- 修复Alpha版本Windows打包重命名问题
- 修复MacOS端口切换崩溃问题
- 解决Linux持续集成更新器问题
- 修复静默启动后热键失效问题
- 修正TypeScript代理组类型定义
- 修复Windows托盘图标空白问题
- 优化远程目标地址显示(替换旧版IP展示)
### 交互体验
- 统一多平台托盘图标点击行为
- 优化代理列表滚动流畅度
- 改进日志搜索功能与数据管理
- 重构热键管理逻辑,修复托盘冻结问题
- 优化托盘网速显示样式
- 增强字母导航工具提示的动态响应
### 国际化
- 新增配置检查多语言支持
- 添加轻量级模式多语言文本
- 完善多语言翻译内容
### 维护更新
- 将默认TUN协议栈改为gVisor
- 更新Node.js运行版本
- 移除自动生成更新器文件
- 清理废弃代码与未使用组件
- 禁用工作流自动Alpha标签更新
- 更新依赖库版本
- 添加MacOS格式转换函数专项测试
- 优化开发模式日志输出
### 安全增强
- 强化应用启动时的配置验证机制
- 改进脚本验证与异常处理流程
- 修复编译警告(移除无用导入)
---
## v2.0.3
### Notice
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
- 由于更改了服务安装逻辑,每次更新安装需要输入系统密码卸载老版本服务和安装新版本服务,以后可以丝滑使用tun(虚拟网卡)模式
### 2.0.3相对于2.0.2改进修复了:
1. 修复VLess-URL识别网络类型错误 f400f90 #2126
2. 新增系统代理绕过文本校验 c71e18e
3. 修复脚本编辑器UI显示不正确 6197249 #2267
4. 修复Shift热键无效 589324b #2278
5. 新增nushell环境变量复制 d233a84
6. 修复全局扩展脚本无法覆写DNS d22b37c #2235
7. 切换到系统代理相对于稳定的版本 38745d4
8. 修改fake-ip-range网段 0e3b631
9. 修复窗口隐藏后WebSocket未断开连接,减小内存风险 b42d13f
10. 改进系统代理绕过设置 c5c840d
11. 修复i18n翻译文本缺失 b149084
12. 修复双击托盘图标打开面板 f839d3b #2346
13. 修复Windows10窗口白色边框 4f6ca40 #2425
14. 修复Windows窗口状态恢复 4f6ca40
15. 改进保存配置文件自动重启Mihomo内核 0669f7a
16. 改进更新托盘图标性能 d9291d4
17. 修复保存配置后代理列表未更新 542baf9 #2460
18. 新增MacOS托盘显示实时速率,可在"界面设置"中关闭 1b2f1b6
19. 新增托盘菜单显示已设置的快捷键 eeff4d4
20. 新增重载配置文件错误响应"400"时显示更多错误信息 c5989d2 #2492
21. 修复GUI代理状态与菜单显示不一致 13b63b5 #2502
22. 新增默认语言跟随系统语言(无语言支持即为英语),添加了阿拉伯语、印尼语、鞑靼语支持 9655f77 #2940
### Features
- Meta(mihomo)内核升级 1.19.1
- 增加更多语言和托盘语言跟随
- MacOS增加状态栏速率显示
- 托盘显示快捷键
- 重载配置文件错误响应"400"时显示更多错误信息
- 改进保存配置文件自动重启Mihomo内核
### Performance
- 改进更新托盘图标性能
- 窗口隐藏后WebSocket断开连接
---
## v2.0.2
### Notice
- !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
- 因 Tauri 2.0 底层 bug,关闭窗口后保留webview进程,优点是再次打开面板更快,缺点是内存使用略有增加
### 2.0.2相对于2.0.1改进了:
- MacOS 下自定义图标可以支持彩色、单色切换
- 修正了 Linux 下多个内核僵尸进程的问题
- 修正了 DNS ipv6 强制覆盖的逻辑
- 修改了 MacOS tun 模式下覆盖设置 dns 字段的问题
- 修正了 MacOS tray 图标不会随代理模式更改的问题
- 静默启动下重复运行会出现多个实例的bug
- 安装的时候自动删除历史残留启动项
- Tun模式默认是还用内核推荐的 mixed 堆栈
- 改进了默认窗口大小(启动软件窗口不会那么小了)
- 改进了 WebDAV 备份超时时间机制
- 测试菜单添加滚动条
- 改进和修正了 Tun 模式下对设置的覆盖逻辑
- 修复了打开配置出错的问题
- 修复了配置文件无法拖拽添加的问题
- 改善了浅色模式的对比度
### 2.0.1相对于2.0.0改进了:
- 无法从 2.0rc和2.0.0 升级的问题(已经安装了2.0版本的需手动下载安装)
- MacOS 系统下少有的无法安装服务,无法启动的问题,目前更健壮了
- 当系统中没有 yaml 编辑器的情况下,打开文件程序崩溃的问题
- Windows 应用内升级和覆盖安装不会删除老执行文件的问题
- 修改优化了 mac 下 fakeip 段和 dns
- 测试菜单 svg 图标格式检查
- 应用内升级重复安装 vs runtime 的问题
- 修复外部控制下密码有特殊字符认证出错的问题
- 修复恢复 Webdav 备份设置后, Webdav 设置丢失的问题
- 代理页面增加快速回到顶部的按钮
### Breaking changes
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
- 出现 bug 到 issues 中提出;以后不再接受1.x版本的bug反馈。
- 强烈建议完全删除 1.x 老版本再安装此版本 !!使用出现异常的,打开设置-->配置目录 备份 后 删除所有文件 尝试是否正常!!
### Features
- Meta(mihomo)内核升级 1.18.10
- Win 下的系统代理替换为 Shadowsocks/CFW/v2rayN 等成熟的 sysproxy.exe 方案,解决拨号/VPN 环境下无法设置系统代理的问题
- 服务模式改进为启动软件时自动安装,TUN 模式可自由开启不再限制于服务模式
- Mac 下可用 URL Scheme 导入订阅
- 可使用 Ctrl(cmd)+Q 快捷键退出程序
- 成功导入订阅的提示消息
- 能自动选中新导入的订阅
- 日志加入颜色区分
- 改进多处文本表述
- 加入图标 svg 格式检测
- 增加更多 app 调试日志
- 添加 MacOS 下白色桌面的 tray 黑色配色(但会代理系统代理、tun 模式图标失效的问题)
- 增加 Webdav 备份功能
- 添加统一延迟的设置开关
- 添加 Windows 下自动检测并下载 vc runtime 的功能
- 支持显示 mux 和 mptcp 的节点标识
- 延迟测试连接更换 http 的 cp.cloudflare.com/generate_204 (关闭统一延迟的情况下延迟测试结果会有所增加)
- 重构日志记录逻辑,可以收集和筛选所有日志类型了(之前无法记录debug的日志类型)
### Performance
- 优化及重构内核启动管理逻辑
- 优化 TUN 启动逻辑
- 重构和优化 app_handle
- 重构系统代理绕过逻辑
- 移除无用的 PID 创建逻辑
- 优化系统 DNS 设置逻辑
- 后端实现窗口控制
- 重构 MacOS 下的 DNS 设置逻辑
### Bugs Fixes
- 修复已有多个订阅导入新订阅会跳选订阅的问题
- 修复多个 Linux 下的 bug, Tun 模式在 Linux 下目前工作正常
- 修复 Linux wayland 下任务栏图标缺失的问题
- 修复 Linux KDE 桌面环境无法启动的问题
- 移除多余退出变量和钩子
- 修复 MacOS 下 tray 菜单重启 app 失效的问题
- 修复某些特定配置文件载入失败的问题
- 修复 MacOS 下 tun 模式 fakeip 不生效的问题
- 修复 Linux 下 关闭 tun 模式文件报错的问题
- 修复快捷键设置的相关 bug
- 修复 Win 下点左键菜单闪现的问题(Mac 下的操作逻辑相反,默认情况下不管点左/右键均会打开菜单,闪现不属于 bug)
### Known issues
- Windows 下窗口大小无法记忆(等待上游修复)
- Webdav 备份因为安全性和兼容性问题,暂不支持跨平台配置同步
---
## v1.7.7
### Bugs Fixes
- 修复导入订阅没有自动重载(不显示节点)的问题
- 英语状态下修复 Windows 工具栏提示文本超过限制的问题
---
## v1.7.6
### Notice
- Clash Verge Rev 目前已进入稳定周期,日后更新将着重于 bug 修复与内核常规升级
### Features
- Meta(mihomo)内核升级 1.18.7
- 界面细节调整
- 优化服务模式安装逻辑
- 移除无用的 console log
- 能自动选择第一个订阅
### Bugs Fixes
- 修复服务模式安装问题
- 修复 Mac 下的代理绕过 CIDR 写法过滤
- 修复 32 位升级 URL
- 修复不同分组 URL 测试地址配置无效的问题
- 修复 Web UI 下的一处 hostname 参数
---
## v1.7.5
### Features
- 展示局域网 IP 地址信息
- 在设置页面直接复制环境变量
- 优化服务模式安装逻辑
### Performance
- 优化切换订阅速度
- 优化更改端口速度
### Bugs Fixes
- 调整 MacOS 托盘图标大小
- Trojan URI 解析错误
- 卡片拖动显示层级错误
- 代理绕过格式检查错误
- MacOS 下编辑器最大化失败
- MacOS 服务安装失败
- 更改窗口大小导致闪退的问题
---
## v1.7.3
### Features
- 支持可视化编辑订阅代理组
- 支持可视化编辑订阅节点
- 支持可视化编辑订阅规则
- 扩展脚本支持订阅名称参数 `function main(config, profileName)`
### Bugs Fixes
- 代理绕过格式检查错误
---
## v1.7.2
### Break Changes
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
- 订阅扩展配置在订阅右键菜单里进入
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
- MacOS 用户更新后请重新安装服务模式
### Features
- 升级内核到 1.18.6
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
- 各种细节优化和界面性能优化
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.7.1
### Break Changes
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
- 订阅扩展配置在订阅右键菜单里进入
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
- MacOS 用户更新后请重新安装服务模式
### Features
- 升级内核到 1.18.6
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
- 各种细节优化和界面性能优化
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.7.0
### Break Changes
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- MacOS 用户更新后请重新安装服务模式
### Features
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.6.6
### Features
- MacOS 应用签名
- 删除 AppImage
- 应用更新对话框添加下载按钮
- 设置系统代理绕过时保留默认值
- 系统代理绕过设置输入格式检查
### Bugs Fixes
- MacOS 代理组图标无法显示
- RPM 包依赖缺失
---
## v1.6.5
### Features
- 添加 RPM 包支持
- 优化细节
### Bugs Fixes
- MacOS 10.15 编辑器空白的问题
- MacOS 低版本启动白屏的问题
---
## v1.6.4
### Features
- 系统代理支持 PAC 模式
- 允许关闭不使用的端口
- 使用新的应用图标
- MacOS 支持切换托盘图标单色/彩色模式
- CSS 注入支持通过编辑器编辑
- 优化代理组列表性能
- 优化流量图显性能
- 支持波斯语
### Bugs Fixes
- Kill 内核后 Tun 开启缓慢的问题
- 代理绕过为空时使用默认值
- 无法读取剪切板内容
- Windows 下覆盖安装无法内核占用问题
---
## v1.6.2
### Features
- 支持本地文件拖拽导入
- 重新支持 32 位 CPU
- 新增内置 Webview2 版本
- 优化 Merge 逻辑,支持深度合并
- 删除 Merge 配置中的 append/prepend-provider 字段
- 支持更新稳定版内核
### Bugs Fixes
- MacOS DNS 还原失败
- CMD 环境变量格式错误
- Linux 下与 N 卡的兼容性问题
- 修改 Tun 设置不立即生效
---
## v1.6.1
### Features
- 鼠标悬浮显示当前订阅的名称 [#938](https://github.com/clash-verge-rev/clash-verge-rev/pull/938)
- 日志过滤支持正则表达式 [#959](https://github.com/clash-verge-rev/clash-verge-rev/pull/959)
- 更新 Clash 内核到 1.18.4
### Bugs Fixes
- 修复 Linux KDE 环境下系统代理无法开启的问题
- 窗口最大化图标调整 [#924](https://github.com/clash-verge-rev/clash-verge-rev/pull/924)
- 修改 MacOS 托盘点击行为(左键菜单,右键点击事件)
- 修复 MacOS 服务模式安装失败的问题
---
## v1.6.0
### Features
- Meta(mihomo)内核回退 1.18.1(当前新版内核 hy2 协议有 bug,等修复后更新)
- 多处界面细节调整 [#724](https://github.com/clash-verge-rev/clash-verge-rev/pull/724) [#799](https://github.com/clash-verge-rev/clash-verge-rev/pull/799) [#900](https://github.com/clash-verge-rev/clash-verge-rev/pull/900) [#901](https://github.com/clash-verge-rev/clash-verge-rev/pull/901)
- Linux 下新增服务模式
- 新增订阅卡片右键可以打开机场首页
- url-test 支持手动选择、节点组 fixed 节点使用角标展示 [#840](https://github.com/clash-verge-rev/clash-verge-rev/pull/840)
- Clash 配置、Merge 配置提供 JSON Schema 语法支持、连接界面调整 [#887](https://github.com/clash-verge-rev/clash-verge-rev/pull/887)
- 修改 Merge 配置文件默认内容 [#889](https://github.com/clash-verge-rev/clash-verge-rev/pull/889)
- 修改 tun 模式默认 mtu 为 1500,老版本升级,需在 tun 模式设置下“重置为默认值”。
- 使用 npm 安装 meta-json-schema [#895](https://github.com/clash-verge-rev/clash-verge-rev/pull/895)
- 更新部分翻译 [#904](https://github.com/clash-verge-rev/clash-verge-rev/pull/904)
- 支持 ico 格式的任务栏图标
### Bugs Fixes
- 修复 Linux KDE 环境下系统代理无法开启的问题
- 修复延迟检测动画问题
- 窗口最大化图标调整 [#816](https://github.com/clash-verge-rev/clash-verge-rev/pull/816)
- 修复 Windows 某些情况下无法安装服务模式 [#822](https://github.com/clash-verge-rev/clash-verge-rev/pull/822)
- UI 细节修复 [#821](https://github.com/clash-verge-rev/clash-verge-rev/pull/821)
- 修复使用默认编辑器打开配置文件
- 修复内核文件在特定目录也可以更新的问题 [#857](https://github.com/clash-verge-rev/clash-verge-rev/pull/857)
- 修复服务模式的安装目录问题
- 修复删除配置文件的“更新间隔”出现的问题 [#907](https://github.com/clash-verge-rev/clash-verge-rev/issues/907)
### 已知问题(历史遗留问题,暂未找到有效解决方案)
- MacOS M 芯片下服务模式无法安装;临时解决方案:在内核 ⚙️ 下,手动授权,再打开 tun 模式。
- MacOS 下如果删除过网络配置,会导致无法正常打开系统代理;临时解决方案:使用浏览器代理插件或手动配置系统代理。
- Window 拨号连接下无法正确识别并打开系统代理;临时解决方案:使用浏览器代理插件或使用 tun 模式。
---
## v1.5.11
### Features
- Meta(mihomo)内核更新 1.18.2
### Bugs Fixes
- 升级图标无法点击的问题
- 卸载时检查安装目录是否为空
- 代理界面图标重合的问题
---
## v1.5.10
### Features
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

+76 -48
View File
@@ -1,80 +1,108 @@
{
"name": "clash-verge",
"version": "1.5.10",
"version": "2.1.3-alpha",
"license": "GPL-3.0-only",
"scripts": {
"dev": "tauri dev",
"dev:diff": "tauri dev -f verge-dev",
"build": "tauri build",
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev -- --profile fast-dev",
"dev:diff": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev -- --profile fast-dev",
"build": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build",
"build:fast": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build -- --profile fast-release",
"tauri": "tauri",
"web:dev": "vite",
"web:build": "tsc && vite build",
"web:build": "tsc --noEmit && vite build",
"web:serve": "vite preview",
"check": "node scripts/check.mjs",
"updater": "node scripts/updater.mjs",
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
"portable": "node scripts/portable.mjs",
"prepare": "husky install"
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
"fix-alpha-version": "node scripts/alpha_version.mjs",
"prepare": "husky",
"clean": "cd ./src-tauri && cargo clean && cd -"
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^5.15.5",
"@mui/lab": "5.0.0-alpha.149",
"@mui/material": "^5.15.5",
"@mui/x-data-grid": "^6.18.7",
"@tauri-apps/api": "^1.5.3",
"ahooks": "^3.7.8",
"axios": "^1.6.5",
"dayjs": "1.11.5",
"i18next": "^23.7.16",
"@mui/icons-material": "^6.4.2",
"@mui/lab": "6.0.0-beta.25",
"@mui/material": "^6.4.2",
"@mui/x-data-grid": "^7.25.0",
"@tauri-apps/api": "2.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.1",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
"@tauri-apps/plugin-notification": "^2.2.1",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "2.2.0",
"@tauri-apps/plugin-updater": "2.3.0",
"@types/json-schema": "^7.0.15",
"ahooks": "^3.8.4",
"axios": "^1.7.9",
"cli-color": "^2.0.4",
"dayjs": "1.11.13",
"foxact": "^0.2.43",
"glob": "^11.0.1",
"i18next": "^24.2.2",
"js-base64": "^3.7.7",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.34.1",
"nanoid": "^5.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.49.3",
"monaco-editor": "^0.52.2",
"monaco-yaml": "^5.2.3",
"nanoid": "^5.0.9",
"peggy": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.54.2",
"react-i18next": "^13.5.0",
"react-router-dom": "^6.21.2",
"react-markdown": "^9.0.1",
"react-monaco-editor": "^0.56.0",
"react-router-dom": "^6.22.3",
"react-transition-group": "^4.4.5",
"react-virtuoso": "^4.6.2",
"recoil": "^0.7.7",
"snarkdown": "^2.0.0",
"swr": "^1.3.0",
"tar": "^6.2.0"
"react-virtuoso": "^4.6.3",
"sockette": "^2.0.6",
"swr": "^2.3.0",
"tar": "^7.4.3",
"types-pac": "^1.0.3",
"zustand": "^5.0.3"
},
"devDependencies": {
"@actions/github": "^5.1.1",
"@tauri-apps/cli": "^1.5.9",
"@types/fs-extra": "^9.0.13",
"@actions/github": "^6.0.0",
"@tauri-apps/cli": "2.2.7",
"@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/react-transition-group": "^4.4.10",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-transition-group": "^4.4.12",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-react": "^4.2.1",
"adm-zip": "^0.5.10",
"adm-zip": "^0.5.16",
"cross-env": "^7.0.3",
"fs-extra": "^11.2.0",
"https-proxy-agent": "^5.0.1",
"husky": "^7.0.4",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"meta-json-schema": "^1.19.1",
"node-fetch": "^3.3.2",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"sass": "^1.70.0",
"typescript": "^5.3.3",
"vite": "^5.0.11",
"prettier": "^3.4.2",
"pretty-quick": "^4.0.0",
"sass": "^1.83.4",
"terser": "^5.37.0",
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svgr": "^4.2.0"
"vite-plugin-svgr": "^4.3.0"
},
"prettier": {
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"endOfLine": "lf"
}
},
"type": "module",
"packageManager": "pnpm@9.13.2"
}
+6041 -2317
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
import { exec } from "child_process";
import { promisify } from "util";
import fs from "fs/promises";
import path from "path";
/**
* 为Alpha版本重命名版本号
*/
const execPromise = promisify(exec);
/**
* 标准输出HEAD hash
*/
async function getLatestCommitHash() {
try {
const { stdout } = await execPromise("git rev-parse HEAD");
const commitHash = stdout.trim();
// 格式化,只截取前7位字符
const formathash = commitHash.substring(0, 7);
console.log(`Found the latest commit hash code: ${commitHash}`);
return formathash;
} catch (error) {
console.error("pnpm run fix-alpha-version ERROR", error);
}
}
/**
* @param string 传入格式化后的hash
* 将新的版本号写入文件 package.json
*/
async function updatePackageVersion(newVersion) {
// 获取内容根目录
const _dirname = process.cwd();
const packageJsonPath = path.join(_dirname, "package.json");
try {
// 读取文件
const data = await fs.readFile(packageJsonPath, "utf8");
const packageJson = JSON.parse(data);
// 获取键值替换
let result = packageJson.version.replace("alpha", newVersion);
console.log("[INFO]: Current version is: ", result);
packageJson.version = result;
// 写入版本号
await fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
"utf8",
);
console.log(`[INFO]: Alpha version update to: ${newVersion}`);
} catch (error) {
console.error("pnpm run fix-alpha-version ERROR", error);
}
}
const newVersion = await getLatestCommitHash();
updatePackageVersion(newVersion).catch(console.error);
+195 -85
View File
@@ -1,11 +1,14 @@
import fs from "fs-extra";
import fs from "fs";
import fsp from "fs/promises";
import zlib from "zlib";
import tar from "tar";
import { extract } from "tar";
import path from "path";
import AdmZip from "adm-zip";
import fetch from "node-fetch";
import proxyAgent from "https-proxy-agent";
import { HttpsProxyAgent } from "https-proxy-agent";
import { execSync } from "child_process";
import { log_info, log_debug, log_error, log_success } from "./utils.mjs";
import { glob } from "glob";
const cwd = process.cwd();
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
@@ -61,7 +64,7 @@ const META_ALPHA_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-x64": "mihomo-darwin-amd64-compatible",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
@@ -82,7 +85,7 @@ async function getLatestAlphaVersion() {
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
options.agent = new HttpsProxyAgent(httpProxy);
}
try {
const response = await fetch(META_ALPHA_VERSION_URL, {
@@ -91,9 +94,9 @@ async function getLatestAlphaVersion() {
});
let v = await response.text();
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest alpha version: ${META_ALPHA_VERSION}`);
log_info(`Latest alpha version: ${META_ALPHA_VERSION}`);
} catch (error) {
console.error("Error fetching latest alpha version:", error.message);
log_error("Error fetching latest alpha version:", error.message);
process.exit(1);
}
}
@@ -108,7 +111,7 @@ const META_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-x64": "mihomo-darwin-amd64-compatible",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
@@ -129,7 +132,7 @@ async function getLatestReleaseVersion() {
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
options.agent = new HttpsProxyAgent(httpProxy);
}
try {
const response = await fetch(META_VERSION_URL, {
@@ -138,9 +141,9 @@ async function getLatestReleaseVersion() {
});
let v = await response.text();
META_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest release version: ${META_VERSION}`);
log_info(`Latest release version: ${META_VERSION}`);
} catch (error) {
console.error("Error fetching latest release version:", error.message);
log_error("Error fetching latest release version:", error.message);
process.exit(1);
}
}
@@ -150,13 +153,13 @@ async function getLatestReleaseVersion() {
*/
if (!META_MAP[`${platform}-${arch}`]) {
throw new Error(
`clash meta alpha unsupported platform "${platform}-${arch}"`
`clash meta alpha unsupported platform "${platform}-${arch}"`,
);
}
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
throw new Error(
`clash meta alpha unsupported platform "${platform}-${arch}"`
`clash meta alpha unsupported platform "${platform}-${arch}"`,
);
}
@@ -172,8 +175,8 @@ function clashMetaAlpha() {
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
return {
name: "clash-meta-alpha",
targetFile: `clash-meta-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
name: "verge-mihomo-alpha",
targetFile: `verge-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
exeFile,
zipFile,
downloadURL,
@@ -189,8 +192,8 @@ function clashMeta() {
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
return {
name: "clash-meta",
targetFile: `clash-meta-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
name: "verge-mihomo",
targetFile: `verge-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
exeFile,
zipFile,
downloadURL,
@@ -205,44 +208,44 @@ async function resolveSidecar(binInfo) {
const sidecarDir = path.join(cwd, "src-tauri", "sidecar");
const sidecarPath = path.join(sidecarDir, targetFile);
await fs.mkdirp(sidecarDir);
if (!FORCE && (await fs.pathExists(sidecarPath))) return;
await fsp.mkdir(sidecarDir, { recursive: true });
if (!FORCE && fs.existsSync(sidecarPath)) return;
const tempDir = path.join(TEMP_DIR, name);
const tempZip = path.join(tempDir, zipFile);
const tempExe = path.join(tempDir, exeFile);
await fs.mkdirp(tempDir);
await fsp.mkdir(tempDir, { recursive: true });
try {
if (!(await fs.pathExists(tempZip))) {
if (!fs.existsSync(tempZip)) {
await downloadFile(downloadURL, tempZip);
}
if (zipFile.endsWith(".zip")) {
const zip = new AdmZip(tempZip);
zip.getEntries().forEach((entry) => {
console.log(`[DEBUG]: "${name}" entry name`, entry.entryName);
log_debug(`"${name}" entry name`, entry.entryName);
});
zip.extractAllTo(tempDir, true);
await fs.rename(tempExe, sidecarPath);
console.log(`[INFO]: "${name}" unzip finished`);
await fsp.rename(tempExe, sidecarPath);
log_success(`unzip finished: "${name}"`);
} else if (zipFile.endsWith(".tgz")) {
// tgz
await fs.mkdirp(tempDir);
await tar.extract({
await fsp.mkdir(tempDir, { recursive: true });
await extract({
cwd: tempDir,
file: tempZip,
//strip: 1, // 可能需要根据实际的 .tgz 文件结构调整
});
const files = await fs.readdir(tempDir);
console.log(`[DEBUG]: "${name}" files in tempDir:`, files);
const files = await fsp.readdir(tempDir);
log_debug(`"${name}" files in tempDir:`, files);
const extractedFile = files.find((file) => file.startsWith("虚空终端-"));
if (extractedFile) {
const extractedFilePath = path.join(tempDir, extractedFile);
await fs.rename(extractedFilePath, sidecarPath);
console.log(`[INFO]: "${name}" file renamed to "${sidecarPath}"`);
await fsp.rename(extractedFilePath, sidecarPath);
log_success(`"${name}" file renamed to "${sidecarPath}"`);
execSync(`chmod 755 ${sidecarPath}`);
console.log(`[INFO]: "${name}" chmod binary finished`);
log_success(`chmod binary finished: "${name}"`);
} else {
throw new Error(`Expected file not found in ${tempDir}`);
}
@@ -252,16 +255,15 @@ async function resolveSidecar(binInfo) {
const writeStream = fs.createWriteStream(sidecarPath);
await new Promise((resolve, reject) => {
const onError = (error) => {
console.error(`[ERROR]: "${name}" gz failed:`, error.message);
log_error(`"${name}" gz failed:`, error.message);
reject(error);
};
readStream
.pipe(zlib.createGunzip().on("error", onError))
.pipe(writeStream)
.on("finish", () => {
console.log(`[INFO]: "${name}" gunzip finished`);
execSync(`chmod 755 ${sidecarPath}`);
console.log(`[INFO]: "${name}" chmod binary finished`);
log_success(`chmod binary finished: "${name}"`);
resolve();
})
.on("error", onError);
@@ -269,35 +271,58 @@ async function resolveSidecar(binInfo) {
}
} catch (err) {
// 需要删除文件
await fs.remove(sidecarPath);
await fsp.rm(sidecarPath, { recursive: true, force: true });
throw err;
} finally {
// delete temp dir
await fs.remove(tempDir);
await fsp.rm(tempDir, { recursive: true, force: true });
}
}
const resolveSetDnsScript = () =>
resolveResource({
file: "set_dns.sh",
localPath: path.join(cwd, "scripts/set_dns.sh"),
});
const resolveUnSetDnsScript = () =>
resolveResource({
file: "unset_dns.sh",
localPath: path.join(cwd, "scripts/unset_dns.sh"),
});
/**
* download the file to the resources dir
*/
async function resolveResource(binInfo) {
const { file, downloadURL } = binInfo;
const { file, downloadURL, localPath } = binInfo;
const resDir = path.join(cwd, "src-tauri/resources");
const targetPath = path.join(resDir, file);
if (!FORCE && (await fs.pathExists(targetPath))) return;
if (!FORCE && fs.existsSync(targetPath)) return;
await fs.mkdirp(resDir);
await downloadFile(downloadURL, targetPath);
if (downloadURL) {
await fsp.mkdir(resDir, { recursive: true });
await downloadFile(downloadURL, targetPath);
}
console.log(`[INFO]: ${file} finished`);
if (localPath) {
await fs.copyFile(localPath, targetPath, (err) => {
if (err) {
console.error("Error copying file:", err);
} else {
console.log("File was copied successfully");
}
});
log_debug(`copy file finished: "${localPath}"`);
}
log_success(`${file} finished`);
}
/**
* download file and save to `path`
*/
async function downloadFile(url, path) {
*/ async function downloadFile(url, path) {
const options = {};
const httpProxy =
@@ -307,7 +332,7 @@ async function downloadFile(url, path) {
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
options.agent = new HttpsProxyAgent(httpProxy);
}
const response = await fetch(url, {
@@ -316,9 +341,9 @@ async function downloadFile(url, path) {
headers: { "Content-Type": "application/octet-stream" },
});
const buffer = await response.arrayBuffer();
await fs.writeFile(path, new Uint8Array(buffer));
await fsp.writeFile(path, new Uint8Array(buffer));
console.log(`[INFO]: download finished "${url}"`);
log_success(`download finished: ${url}`);
}
// SimpleSC.dll
@@ -329,61 +354,111 @@ const resolvePlugin = async () => {
const tempDir = path.join(TEMP_DIR, "SimpleSC");
const tempZip = path.join(
tempDir,
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip",
);
const tempDll = path.join(tempDir, "SimpleSC.dll");
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
const pluginPath = path.join(pluginDir, "SimpleSC.dll");
await fs.mkdirp(pluginDir);
await fs.mkdirp(tempDir);
if (!FORCE && (await fs.pathExists(pluginPath))) return;
await fsp.mkdir(pluginDir, { recursive: true });
await fsp.mkdir(tempDir, { recursive: true });
if (!FORCE && fs.existsSync(pluginPath)) return;
try {
if (!(await fs.pathExists(tempZip))) {
if (!fs.existsSync(tempZip)) {
await downloadFile(url, tempZip);
}
const zip = new AdmZip(tempZip);
zip.getEntries().forEach((entry) => {
console.log(`[DEBUG]: "SimpleSC" entry name`, entry.entryName);
log_debug(`"SimpleSC" entry name`, entry.entryName);
});
zip.extractAllTo(tempDir, true);
await fs.copyFile(tempDll, pluginPath);
console.log(`[INFO]: "SimpleSC" unzip finished`);
await fsp.cp(tempDll, pluginPath, { recursive: true, force: true });
log_success(`unzip finished: "SimpleSC"`);
} finally {
await fs.remove(tempDir);
await fsp.rm(tempDir, { recursive: true, force: true });
}
};
// service chmod
const resolveServicePermission = async () => {
const serviceExecutables = [
"clash-verge-service*",
"install-service*",
"uninstall-service*",
];
const resDir = path.join(cwd, "src-tauri/resources");
for (let f of serviceExecutables) {
// 使用glob模块来处理通配符
const files = glob.sync(path.join(resDir, f));
for (let filePath of files) {
if (fs.existsSync(filePath)) {
execSync(`chmod 755 ${filePath}`);
log_success(`chmod finished: "${filePath}"`);
}
}
}
};
// 在 resolveResource 函数后添加新函数
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);
await fsp.copyFile(srcPath, targetPath);
log_success(`Copied locale file: ${file}`);
}
log_success("All locale files copied successfully");
} catch (err) {
log_error("Error copying locale files:", err.message);
throw err;
}
}
/**
* main
*/
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
const resolveService = () =>
const resolveService = () => {
let ext = platform === "win32" ? ".exe" : "";
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
resolveResource({
file: "clash-verge-service.exe",
downloadURL: `${SERVICE_URL}/clash-verge-service.exe`,
file: "clash-verge-service" + suffix + ext,
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
});
const resolveInstall = () =>
};
const resolveInstall = () => {
let ext = platform === "win32" ? ".exe" : "";
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
resolveResource({
file: "install-service.exe",
downloadURL: `${SERVICE_URL}/install-service.exe`,
file: "install-service" + suffix + ext,
downloadURL: `${SERVICE_URL}/install-service${ext}`,
});
const resolveUninstall = () =>
};
const resolveUninstall = () => {
let ext = platform === "win32" ? ".exe" : "";
let suffix = platform === "linux" ? "-" + SIDECAR_HOST : "";
resolveResource({
file: "uninstall-service.exe",
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
});
const resolveSetDnsScript = () =>
resolveResource({
file: "set_dns.sh",
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/set_dns.sh`,
});
const resolveUnSetDnsScript = () =>
resolveResource({
file: "unset_dns.sh",
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/unset_dns.sh`,
file: "uninstall-service" + suffix + ext,
downloadURL: `${SERVICE_URL}/uninstall-service${ext}`,
});
};
const resolveMmdb = () =>
resolveResource({
file: "Country.mmdb",
@@ -405,26 +480,30 @@ const resolveEnableLoopback = () =>
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 tasks = [
// { name: "clash", func: resolveClash, retry: 5 },
{
name: "clash-meta-alpha",
name: "verge-mihomo-alpha",
func: () =>
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
retry: 5,
},
{
name: "clash-meta",
name: "verge-mihomo",
func: () =>
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
retry: 5,
},
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
{ name: "service", func: resolveService, retry: 5, winOnly: true },
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
{ name: "set_dns_script", func: resolveSetDnsScript, retry: 5 },
{ name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 },
{ name: "service", func: resolveService, retry: 5 },
{ name: "install", func: resolveInstall, retry: 5 },
{ name: "uninstall", func: resolveUninstall, retry: 5 },
{ name: "mmdb", func: resolveMmdb, retry: 5 },
{ name: "geosite", func: resolveGeosite, retry: 5 },
{ name: "geoip", func: resolveGeoIP, retry: 5 },
@@ -434,19 +513,51 @@ const tasks = [
retry: 5,
winOnly: true,
},
{
name: "service_chmod",
func: resolveServicePermission,
retry: 5,
unixOnly: platform === "linux" || platform === "darwin",
},
{
name: "windows-sysproxy",
func: resolveWinSysproxy,
retry: 5,
winOnly: true,
},
{
name: "set_dns_script",
func: resolveSetDnsScript,
retry: 5,
macosOnly: true,
},
{
name: "unset_dns_script",
func: resolveUnSetDnsScript,
retry: 5,
macosOnly: true,
},
{
name: "locales",
func: resolveLocales,
retry: 2,
},
];
async function runTask() {
const task = tasks.shift();
if (!task) return;
if (task.winOnly && process.platform !== "win32") return runTask();
if (task.unixOnly && platform === "win32") return runTask();
if (task.winOnly && platform !== "win32") return runTask();
if (task.macosOnly && platform !== "darwin") return runTask();
if (task.linuxOnly && platform !== "linux") return runTask();
for (let i = 0; i < task.retry; i++) {
try {
await task.func();
break;
} catch (err) {
console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message);
log_error(`task::${task.name} try ${i} ==`, err.message);
if (i === task.retry - 1) throw err;
}
}
@@ -454,4 +565,3 @@ async function runTask() {
}
runTask();
runTask();
+103
View File
@@ -0,0 +1,103 @@
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";
const target = process.argv.slice(2)[0];
const alpha = process.argv.slice(2)[1];
const ARCH_MAP = {
"x86_64-pc-windows-msvc": "x64",
"i686-pc-windows-msvc": "x86",
"aarch64-pc-windows-msvc": "arm64",
};
const PROCESS_MAP = {
x64: "x64",
ia32: "x86",
arm64: "arm64",
};
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
/// Script for ci
/// 打包绿色版/便携版 (only Windows)
async function resolvePortable() {
if (process.platform !== "win32") return;
const releaseDir = target
? `./src-tauri/target/${target}/release`
: `./src-tauri/target/release`;
const configDir = path.join(releaseDir, ".config");
if (!fs.existsSync(releaseDir)) {
throw new Error("could not found the release dir");
}
await fsp.mkdir(configDir, { recursive: true });
if (!fs.existsSync(path.join(configDir, "PORTABLE"))) {
await fsp.writeFile(path.join(configDir, "PORTABLE"), "");
}
const zip = new AdmZip();
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
zip.addLocalFolder(
path.join(
releaseDir,
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
),
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
);
zip.addLocalFolder(configDir, ".config");
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
const { version } = packageJson;
const zipFile = `Clash.Verge_${version}_${arch}_fixed_webview2_portable.zip`;
zip.writeZip(zipFile);
console.log("[INFO]: create portable zip successfully");
// push release assets
if (process.env.GITHUB_TOKEN === undefined) {
throw new Error("GITHUB_TOKEN is required");
}
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
console.log("[INFO]: upload to ", tag);
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag,
});
let assets = release.assets.filter((x) => {
return x.name === zipFile;
});
if (assets.length > 0) {
let id = assets[0].id;
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: id,
});
}
console.log(release.name);
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: release.id,
name: zipFile,
data: zip.toBuffer(),
});
}
resolvePortable().catch(console.error);
+16 -49
View File
@@ -1,17 +1,20 @@
import fs from "fs-extra";
import fs from "fs";
import path from "path";
import AdmZip from "adm-zip";
import { createRequire } from "module";
import { getOctokit, context } from "@actions/github";
import fsp from "fs/promises";
const target = process.argv.slice(2)[0];
const alpha = process.argv.slice(2)[1];
const ARCH_MAP = {
"x86_64-pc-windows-msvc": "x64",
"aarch64-pc-windows-msvc": "arm64",
};
const PROCESS_MAP = {
x64: "x64",
arm64: "arm64",
};
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
/// Script for ci
/// 打包绿色版/便携版 (only Windows)
async function resolvePortable() {
@@ -22,64 +25,28 @@ async function resolvePortable() {
: `./src-tauri/target/release`;
const configDir = path.join(releaseDir, ".config");
if (!(await fs.pathExists(releaseDir))) {
if (!fs.existsSync(releaseDir)) {
throw new Error("could not found the release dir");
}
await fs.mkdir(configDir);
await fs.createFile(path.join(configDir, "PORTABLE"));
await fsp.mkdir(configDir, { recursive: true });
if (!fs.existsSync(path.join(configDir, "PORTABLE"))) {
await fsp.writeFile(path.join(configDir, "PORTABLE"), "");
}
const zip = new AdmZip();
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-meta.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-meta-alpha.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-verge.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
zip.addLocalFolder(configDir, ".config");
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
const { version } = packageJson;
const zipFile = `Clash.Verge_${version}_${ARCH_MAP[target]}_portable.zip`;
const zipFile = `Clash.Verge_${version}_${arch}_portable.zip`;
zip.writeZip(zipFile);
console.log("[INFO]: create portable zip successfully");
// push release assets
if (process.env.GITHUB_TOKEN === undefined) {
throw new Error("GITHUB_TOKEN is required");
}
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
console.log("[INFO]: upload to ", tag);
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag,
});
let assets = release.assets.filter((x) => {
return x.name === zipFile;
});
if (assets.length > 0) {
let id = assets[0].id;
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: id,
});
}
console.log(release.name);
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: release.id,
name: zipFile,
data: zip.toBuffer(),
});
}
resolvePortable().catch(console.error);
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# 验证IPv4地址格式
function is_valid_ipv4() {
local ip=$1
local IFS='.'
local -a octets
[[ ! $ip =~ ^([0-9]+\.){3}[0-9]+$ ]] && return 1
read -r -a octets <<<"$ip"
[ "${#octets[@]}" -ne 4 ] && return 1
for octet in "${octets[@]}"; do
if ! [[ "$octet" =~ ^[0-9]+$ ]] || ((octet < 0 || octet > 255)); then
return 1
fi
done
return 0
}
# 验证IPv6地址格式
function is_valid_ipv6() {
local ip=$1
if [[ ! $ip =~ ^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]] &&
[[ ! $ip =~ ^(([0-9a-fA-F]{0,4}:){0,7}:|(:[0-9a-fA-F]{0,4}:){0,6}:[0-9a-fA-F]{0,4})$ ]]; then
return 1
fi
return 0
}
# 验证IP地址是否为有效的IPv4或IPv6
function is_valid_ip() {
is_valid_ipv4 "$1" || is_valid_ipv6 "$1"
}
# 检查参数
[ $# -lt 1 ] && echo "Usage: $0 <IP address>" && exit 1
! is_valid_ip "$1" && echo "$1 is not a valid IP address." && exit 1
# 获取网络接口和硬件端口
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}}
')
# 获取当前DNS设置
original_dns=$(networksetup -getdnsservers "$hardware_port")
# 检查当前DNS设置是否有效
is_valid_dns=false
for ip in $original_dns; do
ip=$(echo "$ip" | tr -d '[:space:]')
if [ -n "$ip" ] && (is_valid_ipv4 "$ip" || is_valid_ipv6 "$ip"); then
is_valid_dns=true
break
fi
done
# 更新DNS设置
if [ "$is_valid_dns" = false ]; then
echo "empty" >.original_dns.txt
else
echo "$original_dns" >.original_dns.txt
fi
networksetup -setdnsservers "$hardware_port" "$1"
+20
View File
@@ -0,0 +1,20 @@
#!/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
}
}
')
if [ -f .original_dns.txt ]; then
original_dns=$(cat .original_dns.txt)
networksetup -setdnsservers "$hardware_port" $original_dns
rm -rf .original_dns.txt
fi
+4 -3
View File
@@ -1,4 +1,5 @@
import fs from "fs-extra";
import fs from "fs";
import fsp from "fs/promises";
import path from "path";
const UPDATE_LOG = "UPDATELOG.md";
@@ -12,11 +13,11 @@ export async function resolveUpdateLog(tag) {
const file = path.join(cwd, UPDATE_LOG);
if (!(await fs.pathExists(file))) {
if (!fs.existsSync(file)) {
throw new Error("could not found UPDATELOG.md");
}
const data = await fs.readFile(file).then((d) => d.toString("utf8"));
const data = await fsp.readFile(file, "utf-8");
const map = {};
let p = "";
+157
View File
@@ -0,0 +1,157 @@
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog } from "./updatelog.mjs";
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update-fixed-webview2.json";
const UPDATE_JSON_PROXY = "update-fixed-webview2-proxy.json";
/// generate update.json
/// upload to update tag's release asset
async function resolveUpdater() {
if (process.env.GITHUB_TOKEN === undefined) {
throw new Error("GITHUB_TOKEN is required");
}
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const { data: tags } = await github.rest.repos.listTags({
...options,
per_page: 10,
page: 1,
});
// get the latest publish tag
const tag = tags.find((t) => t.name.startsWith("v"));
console.log(tag);
console.log();
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name), // use updatelog.md
pub_date: new Date().toISOString(),
platforms: {
"windows-x86_64": { signature: "", url: "" },
"windows-aarch64": { signature: "", url: "" },
"windows-x86": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" },
},
};
const promises = latestRelease.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
// win64 url
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip")) {
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip")) {
updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig;
}
// win arm url
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
});
await Promise.allSettled(promises);
console.log(updateData);
// maybe should test the signature as well
// delete the null field
Object.entries(updateData.platforms).forEach(([key, value]) => {
if (!value.url) {
console.log(`[Error]: failed to parse release for "${key}"`);
delete updateData.platforms[key];
}
});
// 生成一个代理github的更新文件
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
const updateDataNew = JSON.parse(JSON.stringify(updateData));
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
if (value.url) {
updateDataNew.platforms[key].url =
"https://download.clashverge.dev/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// update the update.json
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: UPDATE_TAG_NAME,
});
// delete the old assets
for (let asset of updateRelease.assets) {
if (asset.name === UPDATE_JSON_FILE) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === UPDATE_JSON_PROXY) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch(console.error); // do not break the pipeline
}
}
// upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_FILE,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_PROXY,
data: JSON.stringify(updateDataNew, null, 2),
});
}
// get the signature file content
async function getSignature(url) {
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/octet-stream" },
});
return response.text();
}
resolveUpdater().catch(console.error);
+148 -64
View File
@@ -2,9 +2,14 @@ import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog } from "./updatelog.mjs";
// Add stable update JSON filenames
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update.json";
const UPDATE_JSON_PROXY = "update-proxy.json";
// Add alpha update JSON filenames
const ALPHA_TAG_NAME = "updater-alpha";
const ALPHA_UPDATE_JSON_FILE = "update-alpha.json";
const ALPHA_UPDATE_JSON_PROXY = "update-alpha-proxy.json";
/// generate update.json
/// upload to update tag's release asset
@@ -16,26 +21,73 @@ async function resolveUpdater() {
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const { data: tags } = await github.rest.repos.listTags({
...options,
per_page: 10,
page: 1,
});
// Fetch all tags using pagination
let allTags = [];
let page = 1;
const perPage = 100;
// get the latest publish tag
const tag = tags.find((t) => t.name.startsWith("v"));
while (true) {
const { data: pageTags } = await github.rest.repos.listTags({
...options,
per_page: perPage,
page: page,
});
console.log(tag);
allTags = allTags.concat(pageTags);
// Break if we received fewer tags than requested (last page)
if (pageTags.length < perPage) {
break;
}
page++;
}
const tags = allTags;
console.log(`Retrieved ${tags.length} tags in total`);
// More flexible tag detection with regex patterns
const stableTagRegex = /^v\d+\.\d+\.\d+$/; // Matches vX.Y.Z format
// const preReleaseRegex = /^v\d+\.\d+\.\d+-(alpha|beta|rc|pre)/i; // Matches vX.Y.Z-alpha/beta/rc format
const preReleaseRegex = /^(alpha|beta|rc|pre)$/i; // Matches exact alpha/beta/rc/pre tags
// Get the latest stable tag and pre-release tag
const stableTag = tags.find((t) => stableTagRegex.test(t.name));
const preReleaseTag = tags.find((t) => preReleaseRegex.test(t.name));
console.log("All tags:", tags.map((t) => t.name).join(", "));
console.log("Stable tag:", stableTag ? stableTag.name : "None found");
console.log(
"Pre-release tag:",
preReleaseTag ? preReleaseTag.name : "None found",
);
console.log();
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
// Process stable release
if (stableTag) {
await processRelease(github, options, stableTag, false);
}
// Process pre-release if found
if (preReleaseTag) {
await processRelease(github, options, preReleaseTag, true);
}
}
// Process a release (stable or alpha) and generate update files
async function processRelease(github, options, tag, isAlpha) {
if (!tag) return;
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name), // use updatelog.md
notes: await resolveUpdateLog(tag.name).catch(
() => "No changelog available",
),
pub_date: new Date().toISOString(),
platforms: {
win64: { signature: "", url: "" }, // compatible with older formats
@@ -45,34 +97,51 @@ async function resolveUpdater() {
"darwin-intel": { signature: "", url: "" },
"darwin-x86_64": { signature: "", url: "" },
"linux-x86_64": { signature: "", url: "" },
"linux-x86": { signature: "", url: "" },
"linux-i686": { signature: "", url: "" },
"linux-aarch64": { signature: "", url: "" },
"linux-armv7": { signature: "", url: "" },
"windows-x86_64": { signature: "", url: "" },
"windows-aarch64": { signature: "", url: "" },
"windows-x86": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" },
},
};
const promises = latestRelease.assets.map(async (asset) => {
const promises = release.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
// Process all the platform URL and signature data
// win64 url
if (name.endsWith("x64-setup.nsis.zip")) {
if (name.endsWith("x64-setup.exe")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith("x64-setup.nsis.zip.sig")) {
if (name.endsWith("x64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith("x86-setup.exe")) {
updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith("x86-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig;
}
// win arm url
if (name.endsWith("arm64-setup.nsis.zip")) {
if (name.endsWith("arm64-setup.exe")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64-setup.nsis.zip.sig")) {
if (name.endsWith("arm64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
@@ -94,27 +163,22 @@ async function resolveUpdater() {
// darwin url (aarch)
if (name.endsWith("aarch64.app.tar.gz")) {
updateData.platforms["darwin-aarch64"].url = browser_download_url;
// 使linux可以检查更新
updateData.platforms.linux.url = browser_download_url;
updateData.platforms["linux-x86_64"].url = browser_download_url;
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].url = browser_download_url;
updateData.platforms["linux-armv7"].url = browser_download_url;
}
// darwin signature (aarch)
if (name.endsWith("aarch64.app.tar.gz.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["darwin-aarch64"].signature = sig;
}
// linux x64 url
if (name.endsWith("amd64.AppImage.tar.gz")) {
updateData.platforms.linux.url = browser_download_url;
updateData.platforms["linux-x86_64"].url = browser_download_url;
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
updateData.platforms["linux-aarch64"].url = browser_download_url;
updateData.platforms["linux-armv7"].url = browser_download_url;
}
// linux x64 signature
if (name.endsWith("amd64.AppImage.tar.gz.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig;
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].signature = sig;
updateData.platforms["linux-armv7"].signature = sig;
}
@@ -132,55 +196,75 @@ async function resolveUpdater() {
}
});
// 生成一个代理github的更新文件
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
// Generate a proxy update file for accelerated GitHub resources
const updateDataNew = JSON.parse(JSON.stringify(updateData));
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
if (value.url) {
updateDataNew.platforms[key].url =
"https://mirror.ghproxy.com/" + value.url;
"https://download.clashverge.dev/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// update the update.json
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: UPDATE_TAG_NAME,
});
// Get the appropriate updater release based on isAlpha flag
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME;
console.log(
`Processing ${isAlpha ? "alpha" : "stable"} release:`,
releaseTag,
);
// delete the old assets
for (let asset of updateRelease.assets) {
if (asset.name === UPDATE_JSON_FILE) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
try {
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: releaseTag,
});
// File names based on release type
const jsonFile = isAlpha ? ALPHA_UPDATE_JSON_FILE : UPDATE_JSON_FILE;
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY;
// Delete existing assets with these names
for (let asset of updateRelease.assets) {
if (asset.name === jsonFile) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === proxyFile) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch(console.error); // do not break the pipeline
}
}
if (asset.name === UPDATE_JSON_PROXY) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch(console.error); // do not break the pipeline
}
// Upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: jsonFile,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: proxyFile,
data: JSON.stringify(updateDataNew, null, 2),
});
console.log(
`Successfully uploaded ${isAlpha ? "alpha" : "stable"} update files to ${releaseTag}`,
);
} catch (error) {
console.error(
`Failed to process ${isAlpha ? "alpha" : "stable"} release:`,
error.message,
);
}
// upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_FILE,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_PROXY,
data: JSON.stringify(updateDataNew, null, 2),
});
}
// get the signature file content
+11
View File
@@ -0,0 +1,11 @@
import clc from "cli-color";
export const log_success = (msg, ...optionalParams) =>
console.log(clc.green(msg), ...optionalParams);
export const log_error = (msg, ...optionalParams) =>
console.log(clc.red(msg), ...optionalParams);
export const log_info = (msg, ...optionalParams) =>
console.log(clc.bgBlue(msg), ...optionalParams);
var debugMsg = clc.xterm(245);
export const log_debug = (msg, ...optionalParams) =>
console.log(debugMsg(msg), ...optionalParams);
+2
View File
@@ -1,6 +1,8 @@
# Generated by Cargo
# will have compiled files and executables
/target/
gen/
WixTools
resources
sidecar
+4472 -1893
View File
File diff suppressed because it is too large Load Diff
Regular → Executable
+92 -16
View File
@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "1.5.10"
version = "2.1.2"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@@ -9,42 +9,83 @@ default-run = "clash-verge"
edition = "2021"
build = "build.rs"
[package.metadata.bundle]
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "1", features = [] }
tauri-build = { version = "2.0.6", features = [] }
[dependencies]
warp = "0.3"
anyhow = "1.0"
dirs = "5.0"
open = "5.0"
anyhow = "1.0.97"
dirs = "6.0"
open = "5.1"
log = "0.4"
dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4"
sysinfo = "0.30"
boa_engine = "0.18"
chrono = "0.4.40"
sysinfo = "0.33.1"
boa_engine = "0.20.0"
serde_json = "1.0"
serde_yaml = "0.9"
once_cell = "1.18"
once_cell = "1.20.3"
port_scanner = "0.1.5"
delay_timer = "0.11.5"
delay_timer = "0.11.6"
parking_lot = "0.12"
percent-encoding = "2.3.1"
window-shadows = { version = "0.2" }
tokio = { version = "1", features = ["full"] }
window-shadows = { version = "0.2.2" }
tokio = { version = "1.43", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
tauri = { version = "1.6", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] }
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
image = "0.25.5"
imageproc = "0.25.0"
rusttype = "0.9"
tauri = { version = "2.3.1", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
network-interface = { version = "2.0.0", features = ["serde"] }
tauri-plugin-shell = "2.2.0"
tauri-plugin-dialog = "2.2.0"
tauri-plugin-fs = "2.2.0"
tauri-plugin-notification = "2.2.1"
tauri-plugin-process = "2.2.0"
tauri-plugin-clipboard-manager = "2.2.1"
tauri-plugin-deep-link = "2.2.0"
tauri-plugin-devtools = "2.0.0-rc"
url = "2.5.4"
zip = "2.2.3"
reqwest_dav = "0.1.14"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.3.1"
tokio-tungstenite = "0.26.2"
futures = "0.3"
sys-locale = "0.3.1"
async-trait = "0.1.87"
mihomo_api = { path = "./src/crate_mihomo_api" }
ab_glyph = "0.2.29"
tungstenite = "0.26.2"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
deelevate = "0.2.0"
winreg = "0.52.0"
winreg = "0.55.0"
url = "2.5.4"
[target.'cfg(target_os = "linux")'.dependencies]
users = "0.11.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.2.0"
tauri-plugin-global-shortcut = "2.2.0"
tauri-plugin-updater = "2.5.1"
tauri-plugin-window-state = "2.2.1"
#openssl
[features]
@@ -57,3 +98,38 @@ panic = "abort"
codegen-units = 1
lto = true
opt-level = "s"
strip = true
[profile.dev]
incremental = true
[profile.fast-release]
inherits = "release" # 继承 release 的配置
panic = "abort" # 与 release 相同
codegen-units = 256 # 增加编译单元,提升编译速度
lto = false # 禁用 LTO,提升编译速度
opt-level = 0 # 禁用优化,大幅提升编译速度
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[profile.fast-dev]
inherits = "dev" # 继承 dev 的配置
codegen-units = 256 # 增加编译单元,提升编译速度
opt-level = 0 # 禁用优化,进一步提升编译速度
incremental = true # 启用增量编译
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[dev-dependencies]
env_logger = "0.11.0"
mockito = "1.7.0"
tempfile = "3.17.1"
[workspace]
members = [
"src/crate_mihomo_api"
]
-17
View File
@@ -1,17 +0,0 @@
<?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>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Clash Verge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>clash</string>
</array>
</dict>
</array>
</dict>
</plist>
BIN
View File
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
{
"identifier": "desktop-capability",
"platforms": ["macOS", "windows", "linux"],
"webviews": ["main"],
"windows": ["main"],
"permissions": [
"global-shortcut:default",
"updater:default",
"dialog:default",
"dialog:allow-ask",
"dialog:allow-message",
"updater:default",
"updater:allow-check",
"updater:allow-download-and-install",
"process:allow-restart",
"deep-link:default",
"window-state:default",
"window-state:default",
"autostart:default"
]
}
+85
View File
@@ -0,0 +1,85 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": [
"core:default",
"fs:allow-read-file",
"fs:allow-exists",
{
"identifier": "fs:scope",
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
},
"fs:allow-write-file",
{
"identifier": "fs:scope",
"allow": ["$APPDATA/**", "$RESOURCE/../**", "**"]
},
"fs:allow-app-read",
"fs:allow-app-read-recursive",
"fs:allow-appcache-read",
"fs:allow-appcache-read-recursive",
"fs:allow-appconfig-read",
"fs:allow-appconfig-read-recursive",
"core:window:allow-create",
"core:window:allow-center",
"core:window:allow-request-user-attention",
"core:window:allow-set-resizable",
"core:window:allow-set-maximizable",
"core:window:allow-set-minimizable",
"core:window:allow-set-closable",
"core:window:allow-set-title",
"core:window:allow-maximize",
"core:window:allow-unmaximize",
"core:window:allow-minimize",
"core:window:allow-unminimize",
"core:window:allow-show",
"core:window:allow-hide",
"core:window:allow-close",
"core:window:allow-set-decorations",
"core:window:allow-set-always-on-top",
"core:window:allow-set-content-protected",
"core:window:allow-set-size",
"core:window:allow-set-min-size",
"core:window:allow-set-max-size",
"core:window:allow-set-position",
"core:window:allow-set-fullscreen",
"core:window:allow-set-focus",
"core:window:allow-set-icon",
"core:window:allow-set-skip-taskbar",
"core:window:allow-set-cursor-grab",
"core:window:allow-set-cursor-visible",
"core:window:allow-set-cursor-icon",
"core:window:allow-set-cursor-position",
"core:window:allow-set-ignore-cursor-events",
"core:window:allow-start-dragging",
"core:window:allow-maximize",
"core:window:allow-toggle-maximize",
"core:window:allow-unmaximize",
"core:window:allow-minimize",
"core:window:allow-unminimize",
"core:window:allow-set-maximizable",
"core:window:allow-set-minimizable",
"core:webview:allow-print",
"shell:allow-execute",
"shell:allow-open",
"shell:allow-kill",
"shell:allow-spawn",
"shell:allow-stdin-write",
"dialog:allow-open",
"notification:default",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-register-all",
"global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all",
"process:allow-restart",
"process:allow-exit",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"shell:default",
"dialog:default",
"notification:default"
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -2,8 +2,9 @@
Categories={{{categories}}}
Comment={{{comment}}}
Exec={{{exec}}} %u
StartupWMClass={{{exec}}}
Icon={{{icon}}}
Name={{{name}}}
Terminal=false
Type=Application
MimeType=x-scheme-handler/clash;
MimeType=x-scheme-handler/clash;
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
chmod +x /usr/bin/install-service
chmod +x /usr/bin/uninstall-service
chmod +x /usr/bin/clash-verge-service
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
/usr/bin/uninstall-service
@@ -0,0 +1,14 @@
<?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>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.application-groups</key>
<array>
<string>io.github.clash-verge-rev.clash-verge-rev</string>
</array>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
@@ -1,5 +1,6 @@
; This file is copied from https://github.com/tauri-apps/tauri/blob/tauri-v1.5/tooling/bundler/src/bundle/windows/templates/installer.nsi
; and edit to fit the needs of the project. the latest tauri 2.x has a different base nsi script.
RequestExecutionLevel admin
Unicode true
; Set the compression algorithm. Default is LZMA.
@@ -13,8 +14,11 @@ Unicode true
!include FileFunc.nsh
!include x64.nsh
!include WordFunc.nsh
!include "LogicLib.nsh"
!include "StrFunc.nsh"
!include "Win\COM.nsh"
!include "Win\Propkey.nsh"
!include "WinVer.nsh"
!include "LogicLib.nsh"
!addplugindir "$%AppData%\Local\NSIS\"
${StrCase}
${StrLoc}
@@ -149,7 +153,6 @@ Function PageReinstall
; however, this should be fine since the user will have to confirm the uninstallation
; and they can chose to abort it if doesn't make sense.
StrCpy $0 0
wix_loop:
EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on
@@ -323,10 +326,15 @@ Var AppStartMenuFolder
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
; Show run app after installation.
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MUI_PAGE_FINISH
Function RunMainBinary
nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" ""
FunctionEnd
; Uninstaller Pages
; 1. Confirm uninstall page
Var DeleteAppDataCheckbox
@@ -420,55 +428,85 @@ Function .onInit
FunctionEnd
!macro CheckAllVergeProcesses
; Check if Clash Verge.exe is running
nsis_tauri_utils::FindProcess "Clash Verge.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill Clash Verge.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "Clash Verge.exe"
!else
nsis_tauri_utils::KillProcess "Clash Verge.exe"
!endif
${EndIf}
; Check if clash-verge-service.exe is running
; Check if clash-verge-service.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "clash-verge-service.exe"
!else
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill clash-verge-service.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
!else
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
!endif
${EndIf}
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill clash-verge-service.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
!else
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
!endif
${EndIf}
; Check if clash-meta-alpha.exe is running
; Check if verge-mihomo-alpha.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo-alpha.exe"
!else
nsis_tauri_utils::FindProcess "verge-mihomo-alpha.exe"
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill verge-mihomo-alpha.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo-alpha.exe"
!else
nsis_tauri_utils::KillProcess "verge-mihomo-alpha.exe"
!endif
${EndIf}
; Check if verge-mihomo.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo.exe"
!else
nsis_tauri_utils::FindProcess "verge-mihomo.exe"
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill verge-mihomo.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo.exe"
!else
nsis_tauri_utils::KillProcess "verge-mihomo.exe"
!endif
${EndIf}
; Check if clash-meta-alpha.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "clash-meta-alpha.exe"
!else
nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill clash-meta-alpha.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
!else
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
!endif
${EndIf}
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill clash-meta-alpha.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
!else
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
!endif
${EndIf}
; Check if clash-meta.exe is running
; Check if clash-meta.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "clash-meta.exe"
!else
nsis_tauri_utils::FindProcess "clash-meta.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill clash-meta.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
!else
nsis_tauri_utils::KillProcess "clash-meta.exe"
!endif
${EndIf}
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill clash-meta.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
!else
nsis_tauri_utils::KillProcess "clash-meta.exe"
!endif
${EndIf}
!macroend
!macro StartVergeService
@@ -571,7 +609,7 @@ Section WebView2
!if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
DetailPrint "$(webview2Downloading)"
nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
NSISdl::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
Pop $0
${If} $0 == 0
DetailPrint "$(webview2DownloadSuccess)"
@@ -653,11 +691,114 @@ SectionEnd
app_check_done:
!macroend
Var VC_REDIST_URL
Var VC_REDIST_EXE
Section CheckAndInstallVSRuntime
; 检查是否已安装 Visual C++ Redistributable
${If} ${IsNativeARM64}
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.arm64.exe"
StrCpy $VC_REDIST_EXE "vc_redist.arm64.exe"
; 检查关键DLL
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
${ElseIf} ${RunningX64}
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
StrCpy $VC_REDIST_EXE "vc_redist.x64.exe"
; 检查关键DLL
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
${Else}
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.x86.exe"
StrCpy $VC_REDIST_EXE "vc_redist.x86.exe"
; 检查关键DLL
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
${EndIf}
checkInstall:
; 检查注册表
${If} ${RunningX64}
SetRegView 64
ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${ARCH}" "Installed"
${If} $R0 == "1"
Goto Done
${EndIf}
${Else}
ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86" "Installed"
${If} $R0 == "1"
Goto Done
${EndIf}
${EndIf}
; 如果没有安装,则下载并安装
DetailPrint "正在下载 Visual C++ Redistributable..."
nsisdl::download "$VC_REDIST_URL" "$TEMP\$VC_REDIST_EXE"
Pop $0
${If} $0 == "success"
DetailPrint "正在安装 Visual C++ Redistributable..."
ExecWait '"$TEMP\$VC_REDIST_EXE" /quiet /norestart' $0
${If} $0 == 0
DetailPrint "Visual C++ Redistributable 安装成功"
${Else}
DetailPrint "Visual C++ Redistributable 安装失败"
${EndIf}
Delete "$TEMP\$VC_REDIST_EXE"
${Else}
DetailPrint "Visual C++ Redistributable 下载失败"
${EndIf}
Done:
SectionEnd
Section Install
SetOutPath $INSTDIR
nsExec::Exec 'netsh int tcp res'
!insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses
; 清理自启动注册表项
DetailPrint "Cleaning auto-launch registry entries..."
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
SetRegView 64
; 清理旧版本的注册表项 (Clash Verge)
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
${If} $R2 != ""
DeleteRegValue HKCU "$R1" "Clash Verge"
${EndIf}
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
${If} $R2 != ""
DeleteRegValue HKLM "$R1" "Clash Verge"
${EndIf}
; 清理新版本的注册表项 (clash-verge)
ReadRegStr $R2 HKCU "$R1" "clash-verge"
${If} $R2 != ""
DeleteRegValue HKCU "$R1" "clash-verge"
${EndIf}
ReadRegStr $R2 HKLM "$R1" "clash-verge"
${If} $R2 != ""
DeleteRegValue HKLM "$R1" "clash-verge"
${EndIf}
; Delete old files before installation
; Delete clash-verge.desktop
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
Delete "$INSTDIR\Clash Verge.exe"
; Copy main executable
File "${MAINBINARYSRCPATH}"
@@ -730,7 +871,8 @@ Function .onInstSuccess
check_r_flag:
${GetOptions} $CMDLINE "/R" $R0
IfErrors run_done 0
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
${GetOptions} $CMDLINE "/ARGS" $R0
nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" "$R0"
run_done:
FunctionEnd
@@ -744,10 +886,68 @@ Function un.onInit
!insertmacro MUI_UNGETLANGUAGE
FunctionEnd
!macro DeleteAppUserModelId
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_DestinationList} ${IID_ICustomDestinationList} r1 ""
${If} $1 P<> 0
${ICustomDestinationList::DeleteList} $1 '("${BUNDLEID}")'
${IUnknown::Release} $1 ""
${EndIf}
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ApplicationDestinations} ${IID_IApplicationDestinations} r1 ""
${If} $1 P<> 0
${IApplicationDestinations::SetAppID} $1 '("${BUNDLEID}")i.r0'
${If} $0 >= 0
${IApplicationDestinations::RemoveAllDestinations} $1 ''
${EndIf}
${IUnknown::Release} $1 ""
${EndIf}
!macroend
; From https://stackoverflow.com/a/42816728/16993372
!macro UnpinShortcut shortcut
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_StartMenuPin} ${IID_IStartMenuPinnedList} r0 ""
${If} $0 P<> 0
System::Call 'SHELL32::SHCreateItemFromParsingName(ws, p0, g "${IID_IShellItem}", *p0r1)' "${shortcut}"
${If} $1 P<> 0
${IStartMenuPinnedList::RemoveFromList} $0 '(r1)'
${IUnknown::Release} $1 ""
${EndIf}
${IUnknown::Release} $0 ""
${EndIf}
!macroend
Section Uninstall
!insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses
!insertmacro RemoveVergeService
; 清理自启动注册表项
DetailPrint "Cleaning auto-launch registry entries..."
StrCpy $R1 "Software\Microsoft\Windows\CurrentVersion\Run"
SetRegView 64
; 清理旧版本的注册表项 (Clash Verge)
ReadRegStr $R2 HKCU "$R1" "Clash Verge"
${If} $R2 != ""
DeleteRegValue HKCU "$R1" "Clash Verge"
${EndIf}
ReadRegStr $R2 HKLM "$R1" "Clash Verge"
${If} $R2 != ""
DeleteRegValue HKLM "$R1" "Clash Verge"
${EndIf}
; 清理新版本的注册表项 (clash-verge)
ReadRegStr $R2 HKCU "$R1" "clash-verge"
${If} $R2 != ""
DeleteRegValue HKCU "$R1" "clash-verge"
${EndIf}
ReadRegStr $R2 HKLM "$R1" "clash-verge"
${If} $R2 != ""
DeleteRegValue HKLM "$R1" "clash-verge"
${EndIf}
; Delete the app directory and its content from disk
; Copy main executable
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
@@ -756,23 +956,27 @@ Section Uninstall
{{#each resources}}
Delete "$INSTDIR\\{{this.[1]}}"
{{/each}}
Delete "$INSTDIR\resources"
; Delete external binaries
{{#each binaries}}
Delete "$INSTDIR\\{{this}}"
{{/each}}
; Delete clash-verge.desktop
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
Delete "$INSTDIR\Clash Verge.exe"
; Delete uninstaller
Delete "$INSTDIR\uninstall.exe"
${If} $DeleteAppDataCheckboxState == 1
RMDir /R /REBOOTOK "$INSTDIR"
${Else}
{{#each resources_ancestors}}
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
{{/each}}
RMDir "$INSTDIR"
${EndIf}
{{#each resources_ancestors}}
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
{{/each}}
RMDir "$INSTDIR"
!insertmacro DeleteAppUserModelId
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
!insertmacro UnpinShortcut "$DESKTOP\${MAINBINARYNAME}.lnk"
; Remove start menu shortcut
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
@@ -815,13 +1019,39 @@ Function SkipIfPassive
${IfThen} $PassiveMode == 1 ${|} Abort ${|}
FunctionEnd
!macro SetLnkAppUserModelId shortcut
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 ""
${If} $0 P<> 0
${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}",.r1)'
${If} $1 P<> 0
${IPersistFile::Load} $1 '("${shortcut}", ${STGM_READWRITE})'
${IUnknown::QueryInterface} $0 '("${IID_IPropertyStore}",.r2)'
${If} $2 P<> 0
System::Call 'Oleaut32::SysAllocString(w "${BUNDLEID}") i.r3'
System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})p.r4'
System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_BSTR},,&i4 $3)p.r5'
${IPropertyStore::SetValue} $2 '($4,$5)'
System::Call 'Oleaut32::SysFreeString($3)'
System::Free $4
System::Free $5
${IPropertyStore::Commit} $2 ""
${IUnknown::Release} $2 ""
${IPersistFile::Save} $1 '("${shortcut}",1)'
${EndIf}
${IUnknown::Release} $1 ""
${EndIf}
${IUnknown::Release} $0 ""
${EndIf}
!macroend
Function CreateDesktopShortcut
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
!insertmacro SetLnkAppUserModelId "$DESKTOP\${MAINBINARYNAME}.lnk"
FunctionEnd
Function CreateStartMenuShortcut
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
FunctionEnd
+198
View File
@@ -0,0 +1,198 @@
use super::CmdResult;
use crate::{feat, utils::dirs, wrap_err};
use tauri::Manager;
/// 打开应用程序所在目录
#[tauri::command]
pub fn open_app_dir() -> CmdResult<()> {
let app_dir = wrap_err!(dirs::app_home_dir())?;
wrap_err!(open::that(app_dir))
}
/// 打开核心所在目录
#[tauri::command]
pub fn open_core_dir() -> CmdResult<()> {
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
wrap_err!(open::that(core_dir))
}
/// 打开日志目录
#[tauri::command]
pub fn open_logs_dir() -> CmdResult<()> {
let log_dir = wrap_err!(dirs::app_logs_dir())?;
wrap_err!(open::that(log_dir))
}
/// 打开网页链接
#[tauri::command]
pub fn open_web_url(url: String) -> CmdResult<()> {
wrap_err!(open::that(url))
}
/// 打开/关闭开发者工具
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_webview_window("main") {
if !window.is_devtools_open() {
window.open_devtools();
} else {
window.close_devtools();
}
}
}
/// 退出应用
#[tauri::command]
pub fn exit_app() {
feat::quit(Some(0));
}
/// 重启应用
#[tauri::command]
pub async fn restart_app() -> CmdResult<()> {
feat::restart_app();
Ok(())
}
/// 获取便携版标识
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
}
/// 获取应用目录
#[tauri::command]
pub fn get_app_dir() -> CmdResult<String> {
let app_home_dir = wrap_err!(dirs::app_home_dir())?
.to_string_lossy()
.to_string();
Ok(app_home_dir)
}
/// 下载图标缓存
#[tauri::command]
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(&name);
// 如果文件已存在,直接返回路径
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
}
// 确保缓存目录存在
if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir);
}
// 使用临时文件名来下载
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
// 下载文件到临时位置
let response = wrap_err!(reqwest::get(&url).await)?;
// 检查内容类型是否为图片
let content_type = response.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let is_image = content_type.starts_with("image/");
// 获取响应内容
let content = wrap_err!(response.bytes().await)?;
// 检查内容是否为HTML (针对CDN错误页面)
let is_html = content.len() > 15 &&
(content.starts_with(b"<!DOCTYPE html") ||
content.starts_with(b"<html") ||
content.starts_with(b"<?xml"));
// 只有当内容确实是图片时才保存
if is_image && !is_html {
{
let mut file = match std::fs::File::create(&temp_path) {
Ok(file) => file,
Err(_) => {
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
} else {
return Err("Failed to create temporary file".into());
}
}
};
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {},
Err(_) => {
let _ = std::fs::remove_file(&temp_path);
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
}
}
}
} else {
let _ = std::fs::remove_file(&temp_path);
}
Ok(icon_path.to_string_lossy().to_string())
} else {
let _ = std::fs::remove_file(&temp_path);
Err(format!("下载的内容不是有效图片: {}", url).into())
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct IconInfo {
name: String,
previous_t: String,
current_t: String,
}
/// 复制图标文件
#[tauri::command]
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::fs;
use std::path::Path;
let file_path = Path::new(&path);
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
if !icon_dir.exists() {
let _ = fs::create_dir_all(&icon_dir);
}
let ext = match file_path.extension() {
Some(e) => e.to_string_lossy().to_string(),
None => "ico".to_string(),
};
let dest_path = icon_dir.join(format!(
"{0}-{1}.{ext}",
icon_info.name, icon_info.current_t
));
if file_path.exists() {
if icon_info.previous_t.trim() != "" {
fs::remove_file(
icon_dir.join(format!("{0}-{1}.png", icon_info.name, icon_info.previous_t)),
)
.unwrap_or_default();
fs::remove_file(
icon_dir.join(format!("{0}-{1}.ico", icon_info.name, icon_info.previous_t)),
)
.unwrap_or_default();
}
match fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),
}
} else {
Err("file not found".to_string())
}
}
+220
View File
@@ -0,0 +1,220 @@
use super::CmdResult;
use crate::{config::*, core::*, feat, module::mihomo::MihomoManager, wrap_err};
use serde_yaml::Mapping;
/// 复制Clash环境变量
#[tauri::command]
pub fn copy_clash_env() -> CmdResult {
feat::copy_clash_env();
Ok(())
}
/// 获取Clash信息
#[tauri::command]
pub fn get_clash_info() -> CmdResult<ClashInfo> {
Ok(Config::clash().latest().get_client_info())
}
/// 修改Clash配置
#[tauri::command]
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
wrap_err!(feat::patch_clash(payload).await)
}
/// 修改Clash模式
#[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult {
Ok(feat::change_clash_mode(payload))
}
/// 切换Clash核心
#[tauri::command]
pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>> {
log::info!(target: "app", "changing core to {clash_core}");
match CoreManager::global()
.change_core(Some(clash_core.clone()))
.await
{
Ok(_) => {
log::info!(target: "app", "core changed to {clash_core}");
handle::Handle::notice_message("config_core::change_success", &clash_core);
handle::Handle::refresh_clash();
Ok(None)
}
Err(err) => {
let error_msg = err.to_string();
log::error!(target: "app", "failed to change core: {error_msg}");
handle::Handle::notice_message("config_core::change_error", &error_msg);
Ok(Some(error_msg))
}
}
}
/// 重启核心
#[tauri::command]
pub async fn restart_core() -> CmdResult {
wrap_err!(CoreManager::global().restart_core().await)
}
/// 获取代理延迟
#[tauri::command]
pub async fn clash_api_get_proxy_delay(
name: String,
url: Option<String>,
timeout: i32,
) -> CmdResult<serde_json::Value> {
MihomoManager::global()
.test_proxy_delay(&name, url, timeout)
.await
}
/// 测试URL延迟
#[tauri::command]
pub async fn test_delay(url: String) -> CmdResult<u32> {
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
}
/// 保存DNS配置到单独文件
#[tauri::command]
pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
use crate::utils::dirs;
use serde_yaml;
use std::fs;
// 获取DNS配置文件路径
let dns_path = dirs::app_home_dir()
.map_err(|e| e.to_string())?
.join("dns_config.yaml");
// 保存DNS配置到文件
let yaml_str = serde_yaml::to_string(&dns_config).map_err(|e| e.to_string())?;
fs::write(&dns_path, yaml_str).map_err(|e| e.to_string())?;
log::info!(target: "app", "DNS config saved to {:?}", dns_path);
Ok(())
}
/// 应用或撤销DNS配置
#[tauri::command]
pub fn apply_dns_config(apply: bool) -> CmdResult {
use crate::config::Config;
use crate::core::{handle, CoreManager};
use crate::utils::dirs;
use tauri::async_runtime;
// 使用spawn来处理异步操作
async_runtime::spawn(async move {
if apply {
// 读取DNS配置文件
let dns_path = match dirs::app_home_dir() {
Ok(path) => path.join("dns_config.yaml"),
Err(e) => {
log::error!(target: "app", "Failed to get home dir: {}", e);
return;
}
};
if !dns_path.exists() {
log::warn!(target: "app", "DNS config file not found");
return;
}
let dns_yaml = match std::fs::read_to_string(&dns_path) {
Ok(content) => content,
Err(e) => {
log::error!(target: "app", "Failed to read DNS config: {}", e);
return;
}
};
// 解析DNS配置并创建patch
let patch_config = match serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {
Ok(config) => {
let mut patch = serde_yaml::Mapping::new();
patch.insert("dns".into(), config.into());
patch
}
Err(e) => {
log::error!(target: "app", "Failed to parse DNS config: {}", e);
return;
}
};
log::info!(target: "app", "Applying DNS config from file");
// 重新生成配置,确保DNS配置被正确应用
// 这里不调用patch_clash以避免将DNS配置写入config.yaml
Config::runtime()
.latest()
.patch_config(patch_config.clone());
// 首先重新生成配置
if let Err(err) = Config::generate().await {
log::error!(target: "app", "Failed to regenerate config with DNS: {}", err);
return;
}
// 然后应用新配置
if let Err(err) = CoreManager::global().update_config().await {
log::error!(target: "app", "Failed to apply config with DNS: {}", err);
} else {
log::info!(target: "app", "DNS config successfully applied");
handle::Handle::refresh_clash();
}
} else {
// 当关闭DNS设置时,不需要对配置进行任何修改
// 直接重新生成配置,让enhance函数自动跳过DNS配置的加载
log::info!(target: "app", "DNS settings disabled, regenerating config");
// 重新生成配置
if let Err(err) = Config::generate().await {
log::error!(target: "app", "Failed to regenerate config: {}", err);
return;
}
// 应用新配置
match CoreManager::global().update_config().await {
Ok(_) => {
log::info!(target: "app", "Config regenerated successfully");
handle::Handle::refresh_clash();
}
Err(err) => {
log::error!(target: "app", "Failed to apply regenerated config: {}", err);
}
}
}
});
Ok(())
}
/// 检查DNS配置文件是否存在
#[tauri::command]
pub fn check_dns_config_exists() -> CmdResult<bool> {
use crate::utils::dirs;
let dns_path = dirs::app_home_dir()
.map_err(|e| e.to_string())?
.join("dns_config.yaml");
Ok(dns_path.exists())
}
/// 获取DNS配置文件内容
#[tauri::command]
pub async fn get_dns_config_content() -> CmdResult<String> {
use crate::utils::dirs;
use std::fs;
let dns_path = dirs::app_home_dir()
.map_err(|e| e.to_string())?
.join("dns_config.yaml");
if !dns_path.exists() {
return Err("DNS config file not found".into());
}
let content = fs::read_to_string(&dns_path).map_err(|e| e.to_string())?;
Ok(content)
}
+32
View File
@@ -0,0 +1,32 @@
use anyhow::Result;
// Common result type used by command functions
pub type CmdResult<T = ()> = Result<T, String>;
// Command modules
pub mod profile;
pub mod validate;
pub mod uwp;
pub mod webdav;
pub mod app;
pub mod network;
pub mod clash;
pub mod verge;
pub mod runtime;
pub mod save_profile;
pub mod system;
pub mod proxy;
// Re-export all command functions for backwards compatibility
pub use profile::*;
pub use validate::*;
pub use uwp::*;
pub use webdav::*;
pub use app::*;
pub use network::*;
pub use clash::*;
pub use verge::*;
pub use runtime::*;
pub use save_profile::*;
pub use system::*;
pub use proxy::*;
+64
View File
@@ -0,0 +1,64 @@
use crate::wrap_err;
use super::CmdResult;
use sysproxy::{Autoproxy, Sysproxy};
use serde_yaml::Mapping;
use network_interface::NetworkInterface;
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Sysproxy::get_system_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert(
"server".into(),
format!("{}:{}", current.host, current.port).into(),
);
map.insert("bypass".into(), current.bypass.into());
Ok(map)
}
/// get the system proxy
#[tauri::command]
pub fn get_auto_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Autoproxy::get_auto_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert("url".into(), current.url.into());
Ok(map)
}
/// 获取网络接口列表
#[tauri::command]
pub fn get_network_interfaces() -> Vec<String> {
use sysinfo::Networks;
let mut result = Vec::new();
let networks = Networks::new_with_refreshed_list();
for (interface_name, _) in &networks {
result.push(interface_name.clone());
}
result
}
/// 获取网络接口详细信息
#[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::NetworkInterface;
use network_interface::NetworkInterfaceConfig;
let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?;
let mut result = Vec::new();
for interface in interfaces {
if names.contains(&interface.name) {
result.push(interface);
}
}
Ok(result)
}
+176
View File
@@ -0,0 +1,176 @@
use crate::{
config::*,
core::*,
feat,
utils::{dirs, help},
log_err, ret_err, wrap_err,
};
use super::CmdResult;
/// 获取配置文件列表
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
let _ = tray::Tray::global().update_menu();
Ok(Config::profiles().data().clone())
}
/// 增强配置文件
#[tauri::command]
pub async fn enhance_profiles() -> CmdResult {
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[enhance_profiles] 配置更新成功");
log_err!(tray::Tray::global().update_tooltip());
handle::Handle::refresh_clash();
Ok(())
}
Ok((false, error_msg)) => {
println!("[enhance_profiles] 配置验证失败: {}", error_msg);
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(())
}
Err(e) => {
println!("[enhance_profiles] 更新过程发生错误: {}", e);
handle::Handle::notice_message("config_validate::process_terminated", &e.to_string());
Ok(())
}
}
}
/// 导入配置文件
#[tauri::command]
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
/// 重新排序配置文件
#[tauri::command]
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
}
/// 创建配置文件
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
/// 更新配置文件
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option).await)
}
/// 删除配置文件
#[tauri::command]
pub async fn delete_profile(index: String) -> CmdResult {
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
if should_update {
wrap_err!(CoreManager::global().update_config().await)?;
handle::Handle::refresh_clash();
}
Ok(())
}
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(
profiles: IProfiles
) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().latest().current.clone();
println!("[cmd配置patch] 当前配置: {:?}", current_profile);
// 更新profiles配置
println!("[cmd配置patch] 正在更新配置草稿");
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
// 更新配置并进行验证
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[cmd配置patch] 配置更新成功");
handle::Handle::refresh_clash();
let _ = tray::Tray::global().update_tooltip();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(true)
}
Ok((false, error_msg)) => {
println!("[cmd配置patch] 配置验证失败: {}", error_msg);
Config::profiles().discard();
// 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile {
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
};
// 静默恢复,不触发验证
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
println!("[cmd配置patch] 成功恢复到之前的配置");
}
// 发送验证错误通知
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(false)
}
Err(e) => {
println!("[cmd配置patch] 更新过程发生错误: {}", e);
Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", &e.to_string());
Ok(false)
}
}
}
/// 根据profile name修改profiles
#[tauri::command]
pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String
) -> CmdResult<bool> {
let profiles = IProfiles{current: Some(profile_index), items: None};
patch_profiles_config(profiles).await
}
/// 修改某个profile item的
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
Ok(())
}
/// 查看配置文件
#[tauri::command]
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
let file = {
wrap_err!(Config::profiles().latest().get_item(&index))?
.file
.clone()
.ok_or("the file field is null")
}?;
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
if !path.exists() {
ret_err!("the file not found");
}
wrap_err!(help::open_file(app_handle, path))
}
/// 读取配置文件内容
#[tauri::command]
pub fn read_profile_file(index: String) -> CmdResult<String> {
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
Ok(data)
}
+16
View File
@@ -0,0 +1,16 @@
use super::CmdResult;
use crate::module::mihomo::MihomoManager;
#[tauri::command]
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
let mannager = MihomoManager::global();
mannager.refresh_proxies().await.unwrap();
Ok(mannager.get_proxies())
}
#[tauri::command]
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
let mannager = MihomoManager::global();
mannager.refresh_providers_proxies().await.unwrap();
Ok(mannager.get_providers_proxies())
}
+39
View File
@@ -0,0 +1,39 @@
use crate::{
config::*,
wrap_err,
};
use super::CmdResult;
use anyhow::Context;
use std::collections::HashMap;
use serde_yaml::Mapping;
/// 获取运行时配置
#[tauri::command]
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
Ok(Config::runtime().latest().config.clone())
}
/// 获取运行时YAML配置
#[tauri::command]
pub fn get_runtime_yaml() -> CmdResult<String> {
let runtime = Config::runtime();
let runtime = runtime.latest();
let config = runtime.config.as_ref();
wrap_err!(config
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
.and_then(
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
))
}
/// 获取运行时存在的键
#[tauri::command]
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
Ok(Config::runtime().latest().exists_keys.clone())
}
/// 获取运行时日志
#[tauri::command]
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
Ok(Config::runtime().latest().chain_logs.clone())
}
+111
View File
@@ -0,0 +1,111 @@
use crate::{
config::*,
core::*,
utils::dirs,
wrap_err,
};
use super::CmdResult;
use std::fs;
/// 保存profiles的配置
#[tauri::command]
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
if file_data.is_none() {
return Ok(());
}
// 在异步操作前完成所有文件操作
let (file_path, original_content, is_merge_file) = {
let profiles = Config::profiles();
let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件
let is_merge = item.itype.as_ref().map_or(false, |t| t == "merge");
let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
(profiles_dir.join(path), content, is_merge)
};
// 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string();
println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file {
println!("[cmd配置save] 检测到merge文件,只进行语法验证");
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await {
Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置
if let Err(e) = CoreManager::global().update_config().await {
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
}
return Ok(());
}
Ok((false, error_msg)) => {
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 发送合并文件专用错误通知
let result = (false, error_msg.clone());
crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件");
return Ok(());
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
return Err(e.to_string());
}
}
}
// 非merge文件使用完整验证流程
match CoreManager::global().validate_config_file(&file_path_str, None).await {
Ok((true, _)) => {
println!("[cmd配置save] 验证成功");
Ok(())
}
Ok((false, error_msg)) => {
println!("[cmd配置save] 验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js") ||
error_msg.contains("Script syntax error") ||
error_msg.contains("Script must contain a main function") ||
error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error") ||
error_msg.contains("Failed to read file:") ||
(!file_path_str.ends_with(".js") && !is_script_error) {
// 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败,发送通知");
let result = (false, error_msg.clone());
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件");
} else if is_script_error {
// 脚本错误使用专门的通知处理
println!("[cmd配置save] 脚本文件验证失败,发送通知");
let result = (false, error_msg.clone());
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
} else {
// 普通配置错误使用一般通知
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg);
}
Ok(())
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
Err(e.to_string())
}
}
}
+35
View File
@@ -0,0 +1,35 @@
use super::CmdResult;
use crate::core::handle;
use crate::module::sysinfo::PlatformSpecification;
use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
let sysinfo = PlatformSpecification::new();
let info = format!("{:?}", sysinfo);
let app_handle = handle::Handle::global().app_handle().unwrap();
let cliboard = app_handle.clipboard();
if let Err(_) = cliboard.write_text(info) {
log::error!(target: "app", "Failed to write to clipboard");
}
Ok(())
}
/// 获取当前内核运行模式
#[tauri::command]
pub async fn get_running_mode() -> Result<String, String> {
match CoreManager::global().get_running_mode().await {
core::RunningMode::Service => Ok("service".to_string()),
core::RunningMode::Sidecar => Ok("sidecar".to_string()),
core::RunningMode::NotRunning => Ok("not_running".to_string()),
}
}
/// 安装/重装系统服务
#[tauri::command]
pub async fn install_service() -> CmdResult {
wrap_err!(service::reinstall_service().await)
}
+29
View File
@@ -0,0 +1,29 @@
use super::CmdResult;
/// Platform-specific implementation for UWP functionality
#[cfg(windows)]
mod platform {
use super::CmdResult;
use crate::core::win_uwp;
use crate::wrap_err;
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)
}
}
/// Stub implementation for non-Windows platforms
#[cfg(not(windows))]
mod platform {
use super::CmdResult;
pub async fn invoke_uwp_tool() -> CmdResult {
Ok(())
}
}
/// Command exposed to Tauri
#[tauri::command]
pub async fn invoke_uwp_tool() -> CmdResult {
platform::invoke_uwp_tool().await
}
+101
View File
@@ -0,0 +1,101 @@
use crate::core::*;
use super::CmdResult;
/// 发送脚本验证通知消息
#[tauri::command]
pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
handle::Handle::notice_message(&status, &msg);
Ok(())
}
/// 处理脚本验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read script file:") {
"config_validate::script_error"
} else if error_msg.starts_with("Script syntax error:") {
"config_validate::script_syntax_error"
} else if error_msg == "Script must contain a main function" {
"config_validate::script_missing_main"
} else {
// 如果是其他类型错误,作为一般脚本错误处理
"config_validate::script_error"
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}
/// 验证指定脚本文件
#[tauri::command]
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global().validate_config_file(&file_path, None).await {
Ok(result) => {
handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值
},
Err(e) => {
let error_msg = e.to_string();
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
handle::Handle::notice_message("config_validate::process_terminated", &error_msg);
Ok(false)
}
}
}
/// 处理YAML验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
// 检查是否为merge文件
let is_merge_file = file_type.contains("合并");
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read file:") {
"config_validate::yaml_read_error"
} else if error_msg.starts_with("YAML syntax error:") {
if is_merge_file {
"config_validate::merge_syntax_error"
} else {
"config_validate::yaml_syntax_error"
}
} else if error_msg.contains("mapping values are not allowed") {
if is_merge_file {
"config_validate::merge_mapping_error"
} else {
"config_validate::yaml_mapping_error"
}
} else if error_msg.contains("did not find expected key") {
if is_merge_file {
"config_validate::merge_key_error"
} else {
"config_validate::yaml_key_error"
}
} else {
// 如果是其他类型错误,根据文件类型作为一般错误处理
if is_merge_file {
"config_validate::merge_error"
} else {
"config_validate::yaml_error"
}
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}
+20
View File
@@ -0,0 +1,20 @@
use crate::{
config::*,
feat,
wrap_err,
};
use super::CmdResult;
/// 获取Verge配置
#[tauri::command]
pub fn get_verge_config() -> CmdResult<IVergeResponse> {
let verge = Config::verge();
let verge_data = verge.data().clone();
Ok(IVergeResponse::from(verge_data))
}
/// 修改Verge配置
#[tauri::command]
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload, false).await)
}
+51
View File
@@ -0,0 +1,51 @@
use crate::{
core,
config::*,
feat,
wrap_err,
};
use super::CmdResult;
use reqwest_dav::list_cmd::ListFile;
/// 保存 WebDAV 配置
#[tauri::command]
pub async fn save_webdav_config(url: String, username: String, password: String) -> CmdResult<()> {
let patch = IVerge {
webdav_url: Some(url),
webdav_username: Some(username),
webdav_password: Some(password),
..IVerge::default()
};
Config::verge().draft().patch_config(patch.clone());
Config::verge().apply();
Config::verge()
.data()
.save_file()
.map_err(|err| err.to_string())?;
core::backup::WebDavClient::global().reset();
Ok(())
}
/// 创建 WebDAV 备份并上传
#[tauri::command]
pub async fn create_webdav_backup() -> CmdResult<()> {
wrap_err!(feat::create_backup_and_upload_webdav().await)
}
/// 列出 WebDAV 上的备份文件
#[tauri::command]
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
wrap_err!(feat::list_wevdav_backup().await)
}
/// 删除 WebDAV 上的备份文件
#[tauri::command]
pub async fn delete_webdav_backup(filename: String) -> CmdResult<()> {
wrap_err!(feat::delete_webdav_backup(filename).await)
}
/// 从 WebDAV 恢复备份文件
#[tauri::command]
pub async fn restore_webdav_backup(filename: String) -> CmdResult<()> {
wrap_err!(feat::restore_webdav_backup(filename).await)
}
-381
View File
@@ -1,381 +0,0 @@
use crate::{
config::*,
core::*,
feat,
utils::{dirs, help, resolve},
};
use crate::{ret_err, wrap_err};
use anyhow::{Context, Result};
use serde_yaml::Mapping;
use std::collections::{HashMap, VecDeque};
use sysproxy::Sysproxy;
use tauri::{api, Manager};
type CmdResult<T = ()> = Result<T, String>;
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
Ok(Config::profiles().data().clone())
}
#[tauri::command]
pub async fn enhance_profiles() -> CmdResult {
wrap_err!(CoreManager::global().update_config().await)?;
handle::Handle::refresh_clash();
Ok(())
}
#[tauri::command]
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
}
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option).await)
}
#[tauri::command]
pub async fn delete_profile(index: String) -> CmdResult {
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
if should_update {
wrap_err!(CoreManager::global().update_config().await)?;
handle::Handle::refresh_clash();
}
Ok(())
}
/// 修改profiles的
#[tauri::command]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
match CoreManager::global().update_config().await {
Ok(_) => {
handle::Handle::refresh_clash();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(())
}
Err(err) => {
Config::profiles().discard();
log::error!(target: "app", "{err}");
Err(format!("{err}"))
}
}
}
/// 修改某个profile item的
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
wrap_err!(timer::Timer::global().refresh())
}
#[tauri::command]
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
let file = {
wrap_err!(Config::profiles().latest().get_item(&index))?
.file
.clone()
.ok_or("the file field is null")
}?;
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
if !path.exists() {
ret_err!("the file not found");
}
wrap_err!(help::open_file(app_handle, path))
}
#[tauri::command]
pub fn read_profile_file(index: String) -> CmdResult<String> {
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
Ok(data)
}
#[tauri::command]
pub fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
if file_data.is_none() {
return Ok(());
}
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
wrap_err!(item.save_file(file_data.unwrap()))
}
#[tauri::command]
pub fn get_clash_info() -> CmdResult<ClashInfo> {
Ok(Config::clash().latest().get_client_info())
}
#[tauri::command]
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
Ok(Config::runtime().latest().config.clone())
}
#[tauri::command]
pub fn get_runtime_yaml() -> CmdResult<String> {
let runtime = Config::runtime();
let runtime = runtime.latest();
let config = runtime.config.as_ref();
wrap_err!(config
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
.and_then(
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
))
}
#[tauri::command]
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
Ok(Config::runtime().latest().exists_keys.clone())
}
#[tauri::command]
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
Ok(Config::runtime().latest().chain_logs.clone())
}
#[tauri::command]
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
wrap_err!(feat::patch_clash(payload).await)
}
#[tauri::command]
pub fn get_verge_config() -> CmdResult<IVerge> {
Ok(Config::verge().data().clone())
}
#[tauri::command]
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload).await)
}
#[tauri::command]
pub async fn change_clash_core(clash_core: Option<String>) -> CmdResult {
wrap_err!(CoreManager::global().change_core(clash_core).await)
}
/// restart the sidecar
#[tauri::command]
pub async fn restart_sidecar() -> CmdResult {
wrap_err!(CoreManager::global().run_core().await)
}
#[tauri::command]
pub fn grant_permission(_core: String) -> CmdResult {
#[cfg(any(target_os = "macos", target_os = "linux"))]
return wrap_err!(manager::grant_permission(_core));
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
return Err("Unsupported target".into());
}
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Sysproxy::get_system_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert(
"server".into(),
format!("{}:{}", current.host, current.port).into(),
);
map.insert("bypass".into(), current.bypass.into());
Ok(map)
}
#[tauri::command]
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
Ok(logger::Logger::global().get_log())
}
#[tauri::command]
pub fn open_app_dir() -> CmdResult<()> {
let app_dir = wrap_err!(dirs::app_home_dir())?;
wrap_err!(open::that(app_dir))
}
#[tauri::command]
pub fn open_core_dir() -> CmdResult<()> {
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
wrap_err!(open::that(core_dir))
}
#[tauri::command]
pub fn open_logs_dir() -> CmdResult<()> {
let log_dir = wrap_err!(dirs::app_logs_dir())?;
wrap_err!(open::that(log_dir))
}
#[tauri::command]
pub fn open_web_url(url: String) -> CmdResult<()> {
wrap_err!(open::that(url))
}
#[cfg(windows)]
pub mod uwp {
use super::*;
use crate::core::win_uwp;
#[tauri::command]
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)
}
}
#[tauri::command]
pub async fn clash_api_get_proxy_delay(
name: String,
url: Option<String>,
timeout: i32,
) -> CmdResult<clash_api::DelayRes> {
match clash_api::get_proxy_delay(name, url, timeout).await {
Ok(res) => Ok(res),
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
}
#[tauri::command]
pub async fn test_delay(url: String) -> CmdResult<u32> {
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
}
#[tauri::command]
pub fn get_app_dir() -> CmdResult<String> {
let app_home_dir = wrap_err!(dirs::app_home_dir())?
.to_string_lossy()
.to_string();
Ok(app_home_dir)
}
#[tauri::command]
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(name);
if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir);
}
if !icon_path.exists() {
let response = wrap_err!(reqwest::get(url).await)?;
let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
let content = wrap_err!(response.bytes().await)?;
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
Ok(icon_path.to_string_lossy().to_string())
}
#[tauri::command]
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
let file_path = std::path::Path::new(&path);
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
if !icon_dir.exists() {
let _ = std::fs::create_dir_all(&icon_dir);
}
let dest_path = icon_dir.join(name);
if file_path.exists() {
match std::fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),
}
} else {
return Err("file not found".to_string());
}
}
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_window("main") {
if !window.is_devtools_open() {
window.open_devtools();
} else {
window.close_devtools();
}
}
}
#[tauri::command]
pub fn exit_app(app_handle: tauri::AppHandle) {
let _ = resolve::save_window_size_position(&app_handle, true);
resolve::resolve_reset();
api::process::kill_children();
app_handle.exit(0);
std::process::exit(0);
}
#[cfg(windows)]
pub mod service {
use super::*;
use crate::core::win_service;
#[tauri::command]
pub async fn check_service() -> CmdResult<win_service::JsonResponse> {
wrap_err!(win_service::check_service().await)
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
wrap_err!(win_service::install_service().await)
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult {
wrap_err!(win_service::uninstall_service().await)
}
}
#[cfg(not(windows))]
pub mod service {
use super::*;
#[tauri::command]
pub async fn check_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult {
Ok(())
}
}
#[cfg(not(windows))]
pub mod uwp {
use super::*;
#[tauri::command]
pub async fn invoke_uwp_tool() -> CmdResult {
Ok(())
}
}
+14 -7
View File
@@ -13,7 +13,7 @@ pub struct IClashTemp(pub Mapping);
impl IClashTemp {
pub fn new() -> Self {
let template = Self::template();
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
match dirs::clash_path().and_then(|path| help::read_mapping(&path)) {
Ok(mut map) => {
template.0.keys().for_each(|key| {
if !map.contains_key(key) {
@@ -32,13 +32,13 @@ impl IClashTemp {
pub fn template() -> Self {
let mut map = Mapping::new();
let mut tun = Mapping::new();
tun.insert("stack".into(), "gvisor".into());
tun.insert("device".into(), "Meta".into());
tun.insert("enable".into(), false.into());
tun.insert("stack".into(), "mixed".into());
tun.insert("auto-route".into(), true.into());
tun.insert("strict-route".into(), false.into());
tun.insert("auto-detect-interface".into(), true.into());
tun.insert("dns-hijack".into(), vec!["any:53"].into());
tun.insert("mtu".into(), 9000.into());
#[cfg(not(target_os = "windows"))]
map.insert("redir-port".into(), 7895.into());
#[cfg(target_os = "linux")]
@@ -50,9 +50,13 @@ impl IClashTemp {
map.insert("allow-lan".into(), false.into());
map.insert("mode".into(), "rule".into());
map.insert("external-controller".into(), "127.0.0.1:9097".into());
let mut cors_map = Mapping::new();
cors_map.insert("allow-private-network".into(), true.into());
cors_map.insert("allow-origins".into(), vec!["*"].into());
map.insert("secret".into(), "".into());
map.insert("tun".into(), tun.into());
map.insert("external-controller-cors".into(), cors_map.into());
map.insert("unified-delay".into(), true.into());
Self(map)
}
@@ -153,17 +157,20 @@ impl IClashTemp {
}
pub fn guard_mixed_port(config: &Mapping) -> u16 {
let mut port = config
.get("mixed-port")
let raw_value = config.get("mixed-port");
let mut port = raw_value
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7897);
if port == 0 {
port = 7897;
}
port
}
+74 -18
View File
@@ -1,11 +1,14 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{
config::PrfItem,
enhance,
utils::{dirs, help},
core::{handle, CoreManager},
};
use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell;
use std::{env::temp_dir, path::PathBuf};
use std::path::PathBuf;
use tokio::time::{sleep, Duration};
pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
@@ -46,21 +49,74 @@ impl Config {
}
/// 初始化订阅
pub fn init_config() -> Result<()> {
crate::log_err!(Self::generate());
if let Err(err) = Self::generate_file(ConfigType::Run) {
log::error!(target: "app", "{err}");
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
// 如果不存在就将默认的clash文件拿过来
if !runtime_path.exists() {
help::save_yaml(
&runtime_path,
&Config::clash().latest().0,
Some("# Clash Verge Runtime"),
)?;
}
pub async fn init_config() -> Result<()> {
if Self::profiles()
.data()
.get_item(&"Merge".to_string())
.is_err()
{
let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?;
Self::profiles().data().append_item(merge_item.clone())?;
}
if Self::profiles()
.data()
.get_item(&"Script".to_string())
.is_err()
{
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
Self::profiles().data().append_item(script_item.clone())?;
}
// 生成运行时配置
crate::log_err!(Self::generate().await);
// 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run);
let validation_result = if let Ok(_) = config_result {
// 验证配置文件
println!("[首次启动] 开始验证配置");
match CoreManager::global().validate_config().await {
Ok((is_valid, error_msg)) => {
if !is_valid {
println!("[首次启动] 配置验证失败,使用默认最小配置启动: {}", error_msg);
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)
.await?;
Some(("config_validate::boot_error", error_msg))
} else {
println!("[首次启动] 配置验证成功");
Some(("config_validate::success", String::new()))
}
}
Err(err) => {
println!("[首次启动] 验证进程执行失败: {}", err);
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.await?;
Some(("config_validate::process_terminated", String::new()))
}
}
} else {
println!("[首次启动] 生成配置文件失败,使用默认配置");
CoreManager::global()
.use_default_config(
"config_validate::error",
"",
)
.await?;
Some(("config_validate::error", String::new()))
};
// 在单独的任务中发送通知
if let Some((msg_type, msg_content)) = validation_result {
tauri::async_runtime::spawn(async move {
sleep(Duration::from_secs(2)).await;
handle::Handle::notice_message(msg_type, &msg_content);
});
}
Ok(())
}
@@ -68,7 +124,7 @@ impl Config {
pub fn generate_file(typ: ConfigType) -> Result<PathBuf> {
let path = match typ {
ConfigType::Run => dirs::app_home_dir()?.join(RUNTIME_CONFIG),
ConfigType::Check => temp_dir().join(CHECK_CONFIG),
ConfigType::Check => dirs::app_home_dir()?.join(CHECK_CONFIG),
};
let runtime = Config::runtime();
@@ -83,8 +139,8 @@ impl Config {
}
/// 生成订阅存好
pub fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance();
pub async fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance().await;
*Config::runtime().draft() = IRuntime {
config: Some(config),
+95
View File
@@ -0,0 +1,95 @@
use crate::utils::dirs::get_encryption_key;
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Key,
};
use base64::{engine::general_purpose::STANDARD, Engine};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
const NONCE_LENGTH: usize = 12;
/// Encrypt data
pub fn encrypt_data(data: &str) -> Result<String, Box<dyn std::error::Error>> {
let encryption_key = get_encryption_key()?;
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
let cipher = Aes256Gcm::new(key);
// Generate random nonce
let mut nonce = vec![0u8; NONCE_LENGTH];
getrandom::fill(&mut nonce)?;
// Encrypt data
let ciphertext = cipher
.encrypt(nonce.as_slice().into(), data.as_bytes())
.map_err(|e| format!("Encryption failed: {}", e))?;
// Concatenate nonce and ciphertext and encode them in base64
let mut combined = nonce;
combined.extend(ciphertext);
Ok(STANDARD.encode(combined))
}
/// Decrypt data
pub fn decrypt_data(encrypted: &str) -> Result<String, Box<dyn std::error::Error>> {
let encryption_key = get_encryption_key()?;
let key = Key::<Aes256Gcm>::from_slice(&encryption_key);
let cipher = Aes256Gcm::new(key);
// Decode from base64
let data = STANDARD.decode(encrypted)?;
if data.len() < NONCE_LENGTH {
return Err("Invalid encrypted data".into());
}
// Separate nonce and ciphertext
let (nonce, ciphertext) = data.split_at(NONCE_LENGTH);
// Decrypt data
let plaintext = cipher
.decrypt(nonce.into(), ciphertext)
.map_err(|e| format!("Decryption failed: {}", e))?;
String::from_utf8(plaintext).map_err(|e| e.into())
}
/// Serialize encrypted function
pub fn serialize_encrypted<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Serialize,
S: Serializer,
{
// 如果序列化失败,返回 None
let json = match serde_json::to_string(value) {
Ok(j) => j,
Err(_) => return serializer.serialize_none(),
};
// 如果加密失败,返回 None
match encrypt_data(&json) {
Ok(encrypted) => serializer.serialize_str(&encrypted),
Err(_) => serializer.serialize_none(),
}
}
/// Deserialize decrypted function
pub fn deserialize_encrypted<'a, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: for<'de> Deserialize<'de> + Default,
D: Deserializer<'a>,
{
// 如果反序列化字符串失败,返回默认值
let encrypted = match String::deserialize(deserializer) {
Ok(s) => s,
Err(_) => return Ok(T::default()),
};
// 如果解密失败,返回默认值
let decrypted_string = match decrypt_data(&encrypted) {
Ok(data) => data,
Err(_) => return Ok(T::default()),
};
// 如果 JSON 解析失败,返回默认值
match serde_json::from_str(&decrypted_string) {
Ok(value) => Ok(value),
Err(_) => Ok(T::default()),
}
}
+8
View File
@@ -1,6 +1,8 @@
mod clash;
#[allow(clippy::module_inception)]
mod config;
mod draft;
mod encrypt;
mod prfitem;
mod profiles;
mod runtime;
@@ -9,7 +11,13 @@ mod verge;
pub use self::clash::*;
pub use self::config::*;
pub use self::draft::*;
pub use self::encrypt::*;
pub use self::prfitem::*;
pub use self::profiles::*;
pub use self::runtime::*;
pub use self::verge::*;
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
}
"#;
+217 -41
View File
@@ -46,6 +46,10 @@ pub struct PrfItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub option: Option<PrfOption>,
/// profile web page url
#[serde(skip_serializing_if = "Option::is_none")]
pub home: Option<String>,
/// the file data
#[serde(skip)]
pub file_data: Option<String>,
@@ -90,6 +94,16 @@ pub struct PrfOption {
/// default is `false`
#[serde(skip_serializing_if = "Option::is_none")]
pub danger_accept_invalid_certs: Option<bool>,
pub merge: Option<String>,
pub script: Option<String>,
pub rules: Option<String>,
pub proxies: Option<String>,
pub groups: Option<String>,
}
impl PrfOption {
@@ -99,8 +113,15 @@ impl PrfOption {
a.user_agent = b.user_agent.or(a.user_agent);
a.with_proxy = b.with_proxy.or(a.with_proxy);
a.self_proxy = b.self_proxy.or(a.self_proxy);
a.danger_accept_invalid_certs = b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs);
a.danger_accept_invalid_certs = b
.danger_accept_invalid_certs
.or(a.danger_accept_invalid_certs);
a.update_interval = b.update_interval.or(a.update_interval);
a.merge = b.merge.or(a.merge);
a.script = b.script.or(a.script);
a.rules = b.rules.or(a.rules);
a.proxies = b.proxies.or(a.proxies);
a.groups = b.groups.or(a.groups);
Some(a)
}
t => t.0.or(t.1),
@@ -129,17 +150,7 @@ impl PrfItem {
"local" => {
let name = item.name.unwrap_or("Local File".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_local(name, desc, file_data)
}
"merge" => {
let name = item.name.unwrap_or("Merge".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_merge(name, desc)
}
"script" => {
let name = item.name.unwrap_or("Script".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_script(name, desc)
PrfItem::from_local(name, desc, file_data, item.option)
}
typ => bail!("invalid profile item type \"{typ}\""),
}
@@ -147,10 +158,49 @@ impl PrfItem {
/// ## Local type
/// create a new item from name/desc
pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
let uid = help::get_uid("l");
pub fn from_local(
name: String,
desc: String,
file_data: Option<String>,
option: Option<PrfOption>,
) -> Result<PrfItem> {
let uid = help::get_uid("L");
let file = format!("{uid}.yaml");
let opt_ref = option.as_ref();
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone());
let mut script = opt_ref.and_then(|o| o.script.clone());
let mut rules = opt_ref.and_then(|o| o.rules.clone());
let mut proxies = opt_ref.and_then(|o| o.proxies.clone());
let mut groups = opt_ref.and_then(|o| o.groups.clone());
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles().data().append_item(rules_item.clone())?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data()
.append_item(proxies_item.clone())?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles().data().append_item(groups_item.clone())?;
groups = groups_item.uid;
}
Ok(PrfItem {
uid: Some(uid),
itype: Some("local".into()),
@@ -160,7 +210,16 @@ impl PrfItem {
url: None,
selected: None,
extra: None,
option: None,
option: Some(PrfOption {
update_interval,
merge,
script,
rules,
proxies,
groups,
..PrfOption::default()
}),
home: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
})
@@ -177,10 +236,15 @@ impl PrfItem {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let accept_invalid_certs = opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
let accept_invalid_certs =
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone());
let mut script = opt_ref.and_then(|o| o.script.clone());
let mut rules = opt_ref.and_then(|o| o.rules.clone());
let mut proxies = opt_ref.and_then(|o| o.proxies.clone());
let mut groups = opt_ref.and_then(|o| o.groups.clone());
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
// 使用软件自己的代理
@@ -274,24 +338,26 @@ impl PrfItem {
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
),
};
let option = match update_interval {
Some(val) => Some(PrfOption {
update_interval: Some(val),
..PrfOption::default()
}),
let update_interval = match update_interval {
Some(val) => Some(val),
None => match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Ok(val) => Some(val * 60), // hour -> min
Err(_) => None,
},
None => None,
},
};
let uid = help::get_uid("r");
let home = match header.get("profile-web-page-url") {
Some(value) => {
let str_value = value.to_str().unwrap_or("");
Some(str_value.to_string())
}
None => None,
};
let uid = help::get_uid("R");
let file = format!("{uid}.yaml");
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
let data = resp.text_with_charset("utf-8").await?;
@@ -307,6 +373,34 @@ impl PrfItem {
bail!("profile does not contain `proxies` or `proxy-providers`");
}
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles().data().append_item(rules_item.clone())?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data()
.append_item(proxies_item.clone())?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles().data().append_item(groups_item.clone())?;
groups = groups_item.uid;
}
Ok(PrfItem {
uid: Some(uid),
itype: Some("remote".into()),
@@ -316,7 +410,16 @@ impl PrfItem {
url: Some(url.into()),
selected: None,
extra,
option,
option: Some(PrfOption {
update_interval,
merge,
script,
rules,
proxies,
groups,
..PrfOption::default()
}),
home,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(data.into()),
})
@@ -324,38 +427,48 @@ impl PrfItem {
/// ## Merge type (enhance)
/// create the enhanced item by using `merge` rule
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
let uid = help::get_uid("m");
let file = format!("{uid}.yaml");
pub fn from_merge(uid: Option<String>) -> Result<PrfItem> {
let mut id = help::get_uid("m");
let mut template = tmpl::ITEM_MERGE_EMPTY.into();
if let Some(uid) = uid {
id = uid;
template = tmpl::ITEM_MERGE.into();
}
let file = format!("{id}.yaml");
Ok(PrfItem {
uid: Some(uid),
uid: Some(id),
itype: Some("merge".into()),
name: Some(name),
desc: Some(desc),
name: None,
desc: None,
file: Some(file),
url: None,
selected: None,
extra: None,
option: None,
home: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_MERGE.into()),
file_data: Some(template),
})
}
/// ## Script type (enhance)
/// create the enhanced item by using javascript quick.js
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
let uid = help::get_uid("s");
let file = format!("{uid}.js"); // js ext
pub fn from_script(uid: Option<String>) -> Result<PrfItem> {
let mut id = help::get_uid("s");
if let Some(uid) = uid {
id = uid;
}
let file = format!("{id}.js"); // js ext
Ok(PrfItem {
uid: Some(uid),
uid: Some(id),
itype: Some("script".into()),
name: Some(name),
desc: Some(desc),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
@@ -364,6 +477,69 @@ impl PrfItem {
})
}
/// ## Rules type (enhance)
pub fn from_rules() -> Result<PrfItem> {
let uid = help::get_uid("r");
let file = format!("{uid}.yaml"); // yaml ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("rules".into()),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_RULES.into()),
})
}
/// ## Proxies type (enhance)
pub fn from_proxies() -> Result<PrfItem> {
let uid = help::get_uid("p");
let file = format!("{uid}.yaml"); // yaml ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("proxies".into()),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_PROXIES.into()),
})
}
/// ## Groups type (enhance)
pub fn from_groups() -> Result<PrfItem> {
let uid = help::get_uid("g");
let file = format!("{uid}.yaml"); // yaml ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("groups".into()),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_GROUPS.into()),
})
}
/// get the file data
pub fn read_file(&self) -> Result<String> {
if self.file.is_none() {
+206 -18
View File
@@ -1,4 +1,4 @@
use super::prfitem::PrfItem;
use super::{prfitem::PrfItem, PrfOption};
use crate::utils::{dirs, help};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
@@ -11,9 +11,6 @@ pub struct IProfiles {
/// same as PrfConfig.current
pub current: Option<String>,
/// same as PrfConfig.chain
pub chain: Option<Vec<String>>,
/// profile list
pub items: Option<Vec<PrfItem>>,
}
@@ -74,16 +71,11 @@ impl IProfiles {
if let Some(current) = patch.current {
let items = self.items.as_ref().unwrap();
let some_uid = Some(current);
if items.iter().any(|e| e.uid == some_uid) {
self.current = some_uid;
}
}
if let Some(chain) = patch.chain {
self.chain = Some(chain);
}
Ok(())
}
@@ -118,6 +110,7 @@ impl IProfiles {
if item.uid.is_none() {
bail!("the uid should not be null");
}
let uid = item.uid.clone();
// save the file data
// move the field value after save
@@ -135,6 +128,12 @@ impl IProfiles {
.with_context(|| format!("failed to write to file \"{}\"", file))?;
}
if self.current.is_none()
&& (item.itype == Some("remote".to_string()) || item.itype == Some("local".to_string()))
{
self.current = uid;
}
if self.items.is_none() {
self.items = Some(vec![]);
}
@@ -142,6 +141,7 @@ impl IProfiles {
if let Some(items) = self.items.as_mut() {
items.push(item)
}
self.save_file()
}
@@ -211,7 +211,8 @@ impl IProfiles {
if each.uid == some_uid {
each.extra = item.extra;
each.updated = item.updated;
each.home = item.home;
each.option = PrfOption::merge(each.option.clone(), item.option);
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {
@@ -243,9 +244,19 @@ impl IProfiles {
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let item = self.get_item(&uid)?;
let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
let mut items = self.items.take().unwrap_or_default();
let mut index = None;
let mut merge_index = None;
let mut script_index = None;
let mut rules_index = None;
let mut proxies_index = None;
let mut groups_index = None;
// get the index
for (i, _) in items.iter().enumerate() {
@@ -254,7 +265,6 @@ impl IProfiles {
break;
}
}
if let Some(index) = index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
@@ -265,13 +275,102 @@ impl IProfiles {
});
}
}
// get the merge index
for (i, _) in items.iter().enumerate() {
if items[i].uid == merge_uid {
merge_index = Some(i);
break;
}
}
if let Some(index) = merge_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the script index
for (i, _) in items.iter().enumerate() {
if items[i].uid == script_uid {
script_index = Some(i);
break;
}
}
if let Some(index) = script_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the rules index
for (i, _) in items.iter().enumerate() {
if items[i].uid == rules_uid {
rules_index = Some(i);
break;
}
}
if let Some(index) = rules_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the proxies index
for (i, _) in items.iter().enumerate() {
if items[i].uid == proxies_uid {
proxies_index = Some(i);
break;
}
}
if let Some(index) = proxies_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the groups index
for (i, _) in items.iter().enumerate() {
if items[i].uid == groups_uid {
groups_index = Some(i);
break;
}
}
if let Some(index) = groups_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// delete the original uid
if current == uid {
self.current = match !items.is_empty() {
true => items[0].uid.clone(),
false => None,
};
self.current = None;
for item in items.iter() {
if item.itype == Some("remote".to_string())
|| item.itype == Some("local".to_string())
{
self.current = item.uid.clone();
break;
}
}
}
self.items = Some(items);
@@ -288,11 +387,100 @@ impl IProfiles {
Some(file) => dirs::app_profiles_dir()?.join(file),
None => bail!("failed to get the file field"),
};
return help::read_merge_mapping(&file_path);
return help::read_mapping(&file_path);
}
bail!("failed to find the current profile \"uid:{current}\"");
}
_ => Ok(Mapping::new()),
}
}
/// 获取current指向的订阅的merge
pub fn current_merge(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let merge = item.option.as_ref().and_then(|e| e.merge.clone());
return merge;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的script
pub fn current_script(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let script = item.option.as_ref().and_then(|e| e.script.clone());
return script;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的rules
pub fn current_rules(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let rules = item.option.as_ref().and_then(|e| e.rules.clone());
return rules;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的proxies
pub fn current_proxies(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let proxies = item.option.as_ref().and_then(|e| e.proxies.clone());
return proxies;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的groups
pub fn current_groups(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let groups = item.option.as_ref().and_then(|e| e.groups.clone());
return groups;
}
None
}
_ => None,
}
}
/// 判断profile是否是current指向的
pub fn is_current_profile_index(&self, index: String) -> bool {
self.current == Some(index)
}
/// 获取所有的profiles(uid,名称)
pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> {
match self.items.as_ref() {
Some(items) => Some(items.iter().filter_map(|e| {
if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) {
Some((uid, name))
} else {
None
}
}).collect()),
None => None,
}
}
}
+18 -14
View File
@@ -19,27 +19,31 @@ impl IRuntime {
// 这里只更改 allow-lan | ipv6 | log-level | tun
pub fn patch_config(&mut self, patch: Mapping) {
if let Some(config) = self.config.as_mut() {
["allow-lan", "ipv6", "log-level"]
["allow-lan", "ipv6", "log-level", "unified-delay"]
.into_iter()
.for_each(|key| {
if let Some(value) = patch.get(key).to_owned() {
config.insert(key.into(), value.clone());
}
});
let tun = config.get("tun");
let mut tun = tun.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
let patch_tun = patch.get("tun");
let patch_tun = patch_tun.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
use_keys(&patch_tun).into_iter().for_each(|key| {
if let Some(value) = patch_tun.get(&key).to_owned() {
tun.insert(key.into(), value.clone());
}
});
config.insert("tun".into(), Value::from(tun));
if patch_tun.is_some() {
let tun = config.get("tun");
let mut tun = tun.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
let patch_tun = patch_tun.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
use_keys(&patch_tun).into_iter().for_each(|key| {
if let Some(value) = patch_tun.get(&key).to_owned() {
tun.insert(key.into(), value.clone());
}
});
config.insert("tun".into(), Value::from(tun));
}
}
}
}
+249 -26
View File
@@ -1,3 +1,6 @@
use crate::config::DEFAULT_PAC;
use crate::config::{deserialize_encrypted, serialize_encrypted};
use crate::utils::i18n;
use crate::utils::{dirs, help};
use anyhow::Result;
use log::LevelFilter;
@@ -6,9 +9,6 @@ use serde::{Deserialize, Serialize};
/// ### `verge.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IVerge {
/// app listening port for app singleton
pub app_singleton_port: Option<u16>,
/// app log level
/// silent | error | warn | info | debug | trace
pub app_log_level: Option<String>,
@@ -42,6 +42,10 @@ pub struct IVerge {
/// common tray icon
pub common_tray_icon: Option<bool>,
/// tray icon
#[cfg(target_os = "macos")]
pub tray_icon: Option<String>,
/// menu icon
pub menu_icon: Option<String>,
@@ -54,10 +58,6 @@ pub struct IVerge {
/// clash tun mode
pub enable_tun_mode: Option<bool>,
/// windows service mode
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_service_mode: Option<bool>,
/// can the app auto startup
pub enable_auto_launch: Option<bool>,
@@ -70,12 +70,24 @@ pub struct IVerge {
/// enable proxy guard
pub enable_proxy_guard: Option<bool>,
/// enable dns settings - this controls whether dns_config.yaml is applied
pub enable_dns_settings: Option<bool>,
/// always use default bypass
pub use_default_bypass: Option<bool>,
/// set system proxy bypass
pub system_proxy_bypass: Option<String>,
/// proxy guard duration
pub proxy_guard_duration: Option<u64>,
/// use pac mode
pub proxy_auto_config: Option<bool>,
/// pac script content
pub pac_file_content: Option<String>,
/// theme setting
pub theme_setting: Option<IVergeTheme>,
@@ -89,6 +101,9 @@ pub struct IVerge {
/// hotkey map
/// format: {func},{key}
pub hotkeys: Option<Vec<String>>,
/// enable global hotkey
pub enable_global_hotkey: Option<bool>,
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
@@ -108,21 +123,13 @@ pub struct IVerge {
/// proxy 页面布局 列数
pub proxy_layout_column: Option<i32>,
/// 测试网站列表
/// 测试站列表
pub test_list: Option<Vec<IVergeTestItem>>,
/// 日志清理
/// 0: 不清理; 1: 7天; 2: 30天; 3: 90天
pub auto_log_clean: Option<i32>,
/// window size and position
#[serde(skip_serializing_if = "Option::is_none")]
pub window_size_position: Option<Vec<f64>>,
/// window size and position
#[serde(skip_serializing_if = "Option::is_none")]
pub window_is_maximized: Option<bool>,
/// 是否启用随机端口
pub enable_random_port: Option<bool>,
@@ -130,14 +137,56 @@ pub struct IVerge {
#[cfg(not(target_os = "windows"))]
pub verge_redir_port: Option<u16>,
#[cfg(not(target_os = "windows"))]
pub verge_redir_enabled: Option<bool>,
#[cfg(target_os = "linux")]
pub verge_tproxy_port: Option<u16>,
#[cfg(target_os = "linux")]
pub verge_tproxy_enabled: Option<bool>,
pub verge_mixed_port: Option<u16>,
pub verge_socks_port: Option<u16>,
pub verge_socks_enabled: Option<bool>,
pub verge_port: Option<u16>,
pub verge_http_enabled: Option<bool>,
/// WebDAV 配置 (加密存储)
#[serde(
serialize_with = "serialize_encrypted",
deserialize_with = "deserialize_encrypted",
skip_serializing_if = "Option::is_none",
default
)]
pub webdav_url: Option<String>,
/// WebDAV 用户名 (加密存储)
#[serde(
serialize_with = "serialize_encrypted",
deserialize_with = "deserialize_encrypted",
skip_serializing_if = "Option::is_none",
default
)]
pub webdav_username: Option<String>,
/// WebDAV 密码 (加密存储)
#[serde(
serialize_with = "serialize_encrypted",
deserialize_with = "deserialize_encrypted",
skip_serializing_if = "Option::is_none",
default
)]
pub webdav_password: Option<String>,
pub enable_tray_speed: Option<bool>,
/// 轻量模式 - 只保留内核运行
pub enable_lite_mode: Option<bool>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -165,6 +214,21 @@ pub struct IVergeTheme {
}
impl IVerge {
fn get_system_language() -> String {
let sys_lang = sys_locale::get_locale()
.unwrap_or_else(|| String::from("en"))
.to_lowercase();
let lang_code = sys_lang.split(['_', '-']).next().unwrap_or("en");
let supported_languages = i18n::get_supported_languages();
if supported_languages.contains(&lang_code.to_string()) {
lang_code.to_string()
} else {
String::from("en")
}
}
pub fn new() -> Self {
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
Ok(config) => config,
@@ -177,8 +241,8 @@ impl IVerge {
pub fn template() -> Self {
Self {
clash_core: Some("clash-meta".into()),
language: Some("zh".into()),
clash_core: Some("verge-mihomo".into()),
language: Some(Self::get_system_language()),
theme_mode: Some("system".into()),
#[cfg(not(target_os = "windows"))]
env_type: Some("bash".into()),
@@ -188,6 +252,8 @@ impl IVerge {
traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_group_icon: Some(true),
#[cfg(target_os = "macos")]
tray_icon: Some("monochrome".into()),
menu_icon: Some("monochrome".into()),
common_tray_icon: Some(false),
sysproxy_tray_icon: Some(false),
@@ -195,20 +261,36 @@ impl IVerge {
enable_auto_launch: Some(false),
enable_silent_start: Some(false),
enable_system_proxy: Some(false),
proxy_auto_config: Some(false),
pac_file_content: Some(DEFAULT_PAC.into()),
enable_random_port: Some(false),
#[cfg(not(target_os = "windows"))]
verge_redir_port: Some(7895),
#[cfg(not(target_os = "windows"))]
verge_redir_enabled: Some(false),
#[cfg(target_os = "linux")]
verge_tproxy_port: Some(7896),
#[cfg(target_os = "linux")]
verge_tproxy_enabled: Some(false),
verge_mixed_port: Some(7897),
verge_socks_port: Some(7898),
verge_socks_enabled: Some(false),
verge_port: Some(7899),
verge_http_enabled: Some(false),
enable_proxy_guard: Some(false),
use_default_bypass: Some(true),
proxy_guard_duration: Some(30),
auto_close_connection: Some(true),
auto_check_update: Some(true),
enable_builtin_enhanced: Some(true),
auto_log_clean: Some(3),
webdav_url: None,
webdav_username: None,
webdav_password: None,
enable_tray_speed: Some(true),
enable_global_hotkey: Some(true),
enable_lite_mode: Some(false),
enable_dns_settings: Some(true),
..Self::default()
}
}
@@ -239,32 +321,43 @@ impl IVerge {
patch!(traffic_graph);
patch!(enable_memory_usage);
patch!(enable_group_icon);
#[cfg(target_os = "macos")]
patch!(tray_icon);
patch!(menu_icon);
patch!(common_tray_icon);
patch!(sysproxy_tray_icon);
patch!(tun_tray_icon);
patch!(enable_tun_mode);
patch!(enable_service_mode);
patch!(enable_auto_launch);
patch!(enable_silent_start);
patch!(enable_random_port);
#[cfg(not(target_os = "windows"))]
patch!(verge_redir_port);
#[cfg(not(target_os = "windows"))]
patch!(verge_redir_enabled);
#[cfg(target_os = "linux")]
patch!(verge_tproxy_port);
#[cfg(target_os = "linux")]
patch!(verge_tproxy_enabled);
patch!(verge_mixed_port);
patch!(verge_socks_port);
patch!(verge_socks_enabled);
patch!(verge_port);
patch!(verge_http_enabled);
patch!(enable_system_proxy);
patch!(enable_proxy_guard);
patch!(use_default_bypass);
patch!(system_proxy_bypass);
patch!(proxy_guard_duration);
patch!(proxy_auto_config);
patch!(pac_file_content);
patch!(theme_setting);
patch!(web_ui_list);
patch!(clash_core);
patch!(hotkeys);
patch!(enable_global_hotkey);
patch!(auto_close_connection);
patch!(auto_check_update);
@@ -274,8 +367,13 @@ impl IVerge {
patch!(proxy_layout_column);
patch!(test_list);
patch!(auto_log_clean);
patch!(window_size_position);
patch!(window_is_maximized);
patch!(webdav_url);
patch!(webdav_username);
patch!(webdav_password);
patch!(enable_tray_speed);
patch!(enable_lite_mode);
patch!(enable_dns_settings);
}
/// 在初始化前尝试拿到单例端口的值
@@ -284,11 +382,7 @@ impl IVerge {
const SERVER_PORT: u16 = 33331;
#[cfg(feature = "verge-dev")]
const SERVER_PORT: u16 = 11233;
match dirs::verge_path().and_then(|path| help::read_yaml::<IVerge>(&path)) {
Ok(config) => config.app_singleton_port.unwrap_or(SERVER_PORT),
Err(_) => SERVER_PORT, // 这里就不log错误了
}
SERVER_PORT
}
/// 获取日志等级
@@ -308,3 +402,132 @@ impl IVerge {
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct IVergeResponse {
pub app_log_level: Option<String>,
pub language: Option<String>,
pub theme_mode: Option<String>,
pub tray_event: Option<String>,
pub env_type: Option<String>,
pub start_page: Option<String>,
pub startup_script: Option<String>,
pub traffic_graph: Option<bool>,
pub enable_memory_usage: Option<bool>,
pub enable_group_icon: Option<bool>,
pub common_tray_icon: Option<bool>,
#[cfg(target_os = "macos")]
pub tray_icon: Option<String>,
pub menu_icon: Option<String>,
pub sysproxy_tray_icon: Option<bool>,
pub tun_tray_icon: Option<bool>,
pub enable_tun_mode: Option<bool>,
pub enable_auto_launch: Option<bool>,
pub enable_silent_start: Option<bool>,
pub enable_system_proxy: Option<bool>,
pub enable_proxy_guard: Option<bool>,
pub enable_global_hotkey: Option<bool>,
pub use_default_bypass: Option<bool>,
pub system_proxy_bypass: Option<String>,
pub proxy_guard_duration: Option<u64>,
pub proxy_auto_config: Option<bool>,
pub pac_file_content: Option<String>,
pub theme_setting: Option<IVergeTheme>,
pub web_ui_list: Option<Vec<String>>,
pub clash_core: Option<String>,
pub hotkeys: Option<Vec<String>>,
pub auto_close_connection: Option<bool>,
pub auto_check_update: Option<bool>,
pub default_latency_test: Option<String>,
pub default_latency_timeout: Option<i32>,
pub enable_builtin_enhanced: Option<bool>,
pub proxy_layout_column: Option<i32>,
pub test_list: Option<Vec<IVergeTestItem>>,
pub auto_log_clean: Option<i32>,
pub enable_random_port: Option<bool>,
#[cfg(not(target_os = "windows"))]
pub verge_redir_port: Option<u16>,
#[cfg(not(target_os = "windows"))]
pub verge_redir_enabled: Option<bool>,
#[cfg(target_os = "linux")]
pub verge_tproxy_port: Option<u16>,
#[cfg(target_os = "linux")]
pub verge_tproxy_enabled: Option<bool>,
pub verge_mixed_port: Option<u16>,
pub verge_socks_port: Option<u16>,
pub verge_socks_enabled: Option<bool>,
pub verge_port: Option<u16>,
pub verge_http_enabled: Option<bool>,
pub webdav_url: Option<String>,
pub webdav_username: Option<String>,
pub webdav_password: Option<String>,
pub enable_tray_speed: Option<bool>,
pub enable_lite_mode: Option<bool>,
pub enable_dns_settings: Option<bool>,
}
impl From<IVerge> for IVergeResponse {
fn from(verge: IVerge) -> Self {
Self {
app_log_level: verge.app_log_level,
language: verge.language,
theme_mode: verge.theme_mode,
tray_event: verge.tray_event,
env_type: verge.env_type,
start_page: verge.start_page,
startup_script: verge.startup_script,
traffic_graph: verge.traffic_graph,
enable_memory_usage: verge.enable_memory_usage,
enable_group_icon: verge.enable_group_icon,
common_tray_icon: verge.common_tray_icon,
#[cfg(target_os = "macos")]
tray_icon: verge.tray_icon,
menu_icon: verge.menu_icon,
sysproxy_tray_icon: verge.sysproxy_tray_icon,
tun_tray_icon: verge.tun_tray_icon,
enable_tun_mode: verge.enable_tun_mode,
enable_auto_launch: verge.enable_auto_launch,
enable_silent_start: verge.enable_silent_start,
enable_system_proxy: verge.enable_system_proxy,
enable_proxy_guard: verge.enable_proxy_guard,
enable_global_hotkey: verge.enable_global_hotkey,
use_default_bypass: verge.use_default_bypass,
system_proxy_bypass: verge.system_proxy_bypass,
proxy_guard_duration: verge.proxy_guard_duration,
proxy_auto_config: verge.proxy_auto_config,
pac_file_content: verge.pac_file_content,
theme_setting: verge.theme_setting,
web_ui_list: verge.web_ui_list,
clash_core: verge.clash_core,
hotkeys: verge.hotkeys,
auto_close_connection: verge.auto_close_connection,
auto_check_update: verge.auto_check_update,
default_latency_test: verge.default_latency_test,
default_latency_timeout: verge.default_latency_timeout,
enable_builtin_enhanced: verge.enable_builtin_enhanced,
proxy_layout_column: verge.proxy_layout_column,
test_list: verge.test_list,
auto_log_clean: verge.auto_log_clean,
enable_random_port: verge.enable_random_port,
#[cfg(not(target_os = "windows"))]
verge_redir_port: verge.verge_redir_port,
#[cfg(not(target_os = "windows"))]
verge_redir_enabled: verge.verge_redir_enabled,
#[cfg(target_os = "linux")]
verge_tproxy_port: verge.verge_tproxy_port,
#[cfg(target_os = "linux")]
verge_tproxy_enabled: verge.verge_tproxy_enabled,
verge_mixed_port: verge.verge_mixed_port,
verge_socks_port: verge.verge_socks_port,
verge_socks_enabled: verge.verge_socks_enabled,
verge_port: verge.verge_port,
verge_http_enabled: verge.verge_http_enabled,
webdav_url: verge.webdav_url,
webdav_username: verge.webdav_username,
webdav_password: verge.webdav_password,
enable_tray_speed: verge.enable_tray_speed,
enable_lite_mode: verge.enable_lite_mode,
enable_dns_settings: verge.enable_dns_settings,
}
}
}
+226
View File
@@ -0,0 +1,226 @@
use crate::config::Config;
use crate::utils::dirs;
use anyhow::Error;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use reqwest_dav::list_cmd::{ListEntity, ListFile};
use std::collections::HashMap;
use std::env::{consts::OS, temp_dir};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
use zip::write::SimpleFileOptions;
const TIMEOUT_UPLOAD: u64 = 300; // 上传超时 5 分钟
const TIMEOUT_DOWNLOAD: u64 = 300; // 下载超时 5 分钟
const TIMEOUT_LIST: u64 = 3; // 列表超时 30 秒
const TIMEOUT_DELETE: u64 = 3; // 删除超时 30 秒
#[derive(Clone)]
struct WebDavConfig {
url: String,
username: String,
password: String,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
enum Operation {
Upload,
Download,
List,
Delete,
}
impl Operation {
fn timeout(&self) -> u64 {
match self {
Operation::Upload => TIMEOUT_UPLOAD,
Operation::Download => TIMEOUT_DOWNLOAD,
Operation::List => TIMEOUT_LIST,
Operation::Delete => TIMEOUT_DELETE,
}
}
}
pub struct WebDavClient {
config: Arc<Mutex<Option<WebDavConfig>>>,
clients: Arc<Mutex<HashMap<Operation, reqwest_dav::Client>>>,
}
impl WebDavClient {
pub fn global() -> &'static WebDavClient {
static WEBDAV_CLIENT: OnceCell<WebDavClient> = OnceCell::new();
WEBDAV_CLIENT.get_or_init(|| WebDavClient {
config: Arc::new(Mutex::new(None)),
clients: Arc::new(Mutex::new(HashMap::new())),
})
}
async fn get_client(&self, op: Operation) -> Result<reqwest_dav::Client, Error> {
// 先尝试从缓存获取
{
let clients = self.clients.lock();
if let Some(client) = clients.get(&op) {
return Ok(client.clone());
}
}
// 获取或创建配置
let config = {
let mut lock = self.config.lock();
if let Some(cfg) = lock.as_ref() {
cfg.clone()
} else {
let verge = Config::verge().latest().clone();
if verge.webdav_url.is_none()
|| verge.webdav_username.is_none()
|| verge.webdav_password.is_none()
{
let msg = "Unable to create web dav client, please make sure the webdav config is correct".to_string();
return Err(anyhow::Error::msg(msg));
}
let config = WebDavConfig {
url: verge
.webdav_url
.unwrap_or_default()
.trim_end_matches('/')
.to_string(),
username: verge.webdav_username.unwrap_or_default(),
password: verge.webdav_password.unwrap_or_default(),
};
*lock = Some(config.clone());
config
}
};
// 创建新的客户端
let client = reqwest_dav::ClientBuilder::new()
.set_agent(
reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(op.timeout()))
.build()
.unwrap(),
)
.set_host(config.url)
.set_auth(reqwest_dav::Auth::Basic(config.username, config.password))
.build()?;
// 确保备份目录存在
let list_result = client
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
.await;
if list_result.is_err() {
client.mkcol(dirs::BACKUP_DIR).await?;
}
// 缓存客户端
{
let mut clients = self.clients.lock();
clients.insert(op, client.clone());
}
Ok(client)
}
pub fn reset(&self) {
*self.config.lock() = None;
self.clients.lock().clear();
}
pub async fn upload(&self, file_path: PathBuf, file_name: String) -> Result<(), Error> {
let client = self.get_client(Operation::Upload).await?;
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name);
let fut = client.put(webdav_path.as_ref(), fs::read(file_path)?);
timeout(Duration::from_secs(TIMEOUT_UPLOAD), fut).await??;
Ok(())
}
pub async fn download(&self, filename: String, storage_path: PathBuf) -> Result<(), Error> {
let client = self.get_client(Operation::Download).await?;
let path = format!("{}/{}", dirs::BACKUP_DIR, filename);
let fut = async {
let response = client.get(path.as_str()).await?;
let content = response.bytes().await?;
fs::write(&storage_path, &content)?;
Ok::<(), Error>(())
};
timeout(Duration::from_secs(TIMEOUT_DOWNLOAD), fut).await??;
Ok(())
}
pub async fn list(&self) -> Result<Vec<ListFile>, Error> {
let client = self.get_client(Operation::List).await?;
let path = format!("{}/", dirs::BACKUP_DIR);
let fut = async {
let files = client
.list(path.as_str(), reqwest_dav::Depth::Number(1))
.await?;
let mut final_files = Vec::new();
for file in files {
if let ListEntity::File(file) = file {
final_files.push(file);
}
}
Ok::<Vec<ListFile>, Error>(final_files)
};
timeout(Duration::from_secs(TIMEOUT_LIST), fut).await?
}
pub async fn delete(&self, file_name: String) -> Result<(), Error> {
let client = self.get_client(Operation::Delete).await?;
let path = format!("{}/{}", dirs::BACKUP_DIR, file_name);
let fut = client.delete(&path);
timeout(Duration::from_secs(TIMEOUT_DELETE), fut).await??;
Ok(())
}
}
pub fn create_backup() -> Result<(String, PathBuf), Error> {
let now = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string();
let zip_file_name = format!("{}-backup-{}.zip", OS, now);
let zip_path = temp_dir().join(&zip_file_name);
let file = fs::File::create(&zip_path)?;
let mut zip = zip::ZipWriter::new(file);
zip.add_directory("profiles/", SimpleFileOptions::default())?;
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
if let Ok(entries) = fs::read_dir(dirs::app_profiles_dir()?) {
for entry in entries {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() {
let backup_path = format!("profiles/{}", entry.file_name().to_str().unwrap());
zip.start_file(backup_path, options)?;
zip.write_all(fs::read(path).unwrap().as_slice())?;
}
}
}
zip.start_file(dirs::CLASH_CONFIG, options)?;
zip.write_all(fs::read(dirs::clash_path()?)?.as_slice())?;
let mut verge_config: serde_json::Value =
serde_yaml::from_str(&fs::read_to_string(dirs::verge_path()?)?)?;
if let Some(obj) = verge_config.as_object_mut() {
obj.remove("webdav_username");
obj.remove("webdav_password");
obj.remove("webdav_url");
}
zip.start_file(dirs::VERGE_CONFIG, options)?;
zip.write_all(serde_yaml::to_string(&verge_config)?.as_bytes())?;
zip.start_file(dirs::PROFILE_YAML, options)?;
zip.write_all(fs::read(dirs::profiles_path()?)?.as_slice())?;
zip.finish()?;
Ok((zip_file_name, zip_path))
}
-145
View File
@@ -1,145 +0,0 @@
use crate::config::Config;
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::collections::HashMap;
/// PUT /configs
/// path 是绝对路径
pub async fn put_configs(path: &str) -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs");
let mut data = HashMap::new();
data.insert("path", path);
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client.put(&url).headers(headers).json(&data);
let response = builder.send().await?;
match response.status().as_u16() {
204 => Ok(()),
status => {
bail!("failed to put configs with status \"{status}\"")
}
}
}
/// PATCH /configs
pub async fn patch_configs(config: &Mapping) -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs");
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client.patch(&url).headers(headers.clone()).json(config);
builder.send().await?;
Ok(())
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct DelayRes {
delay: u64,
}
/// GET /proxies/{name}/delay
/// 获取代理延迟
pub async fn get_proxy_delay(
name: String,
test_url: Option<String>,
timeout: i32,
) -> Result<DelayRes> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/proxies/{name}/delay");
let default_url = "http://1.1.1.1";
let test_url = test_url
.map(|s| if s.is_empty() { default_url.into() } else { s })
.unwrap_or(default_url.into());
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client
.get(&url)
.headers(headers)
.query(&[("timeout", &format!("{timeout}")), ("url", &test_url)]);
let response = builder.send().await?;
Ok(response.json::<DelayRes>().await?)
}
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
let client = { Config::clash().data().get_client_info() };
let server = format!("http://{}", client.server);
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
if let Some(secret) = client.secret {
let secret = format!("Bearer {}", secret).parse()?;
headers.insert("Authorization", secret);
}
Ok((server, headers))
}
/// 缩短clash的日志
pub fn parse_log(log: String) -> String {
if log.starts_with("time=") && log.len() > 33 {
return (log[33..]).to_owned();
}
if log.len() > 9 {
return (log[9..]).to_owned();
}
log
}
/// 缩短clash -t的错误输出
/// 仅适配 clash p核 8-26、clash meta 1.13.1
pub fn parse_check_output(log: String) -> String {
let t = log.find("time=");
let m = log.find("msg=");
let mr = log.rfind('"');
if let (Some(_), Some(m), Some(mr)) = (t, m, mr) {
let e = match log.find("level=error msg=") {
Some(e) => e + 17,
None => m + 5,
};
if mr > m {
return (log[e..mr]).to_owned();
}
}
let l = log.find("error=");
let r = log.find("path=").or(Some(log.len()));
if let (Some(l), Some(r)) = (l, r) {
return (log[(l + 6)..(r - 1)]).to_owned();
}
log
}
#[test]
fn test_parse_check_output() {
let str1 = r#"xxxx\n time="2022-11-18T20:42:58+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'""#;
let str2 = r#"20:43:49 ERR [Config] configuration file test failed error=proxy 0: unsupport proxy type: hysteria path=xxx"#;
let str3 = r#"
"time="2022-11-18T21:38:01+08:00" level=info msg="Start initial configuration in progress"
time="2022-11-18T21:38:01+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'"
configuration file xxx\n
"#;
let res1 = parse_check_output(str1.into());
let res2 = parse_check_output(str2.into());
let res3 = parse_check_output(str3.into());
println!("res1: {res1}");
println!("res2: {res2}");
println!("res3: {res3}");
assert_eq!(res1, res3);
}
+578 -267
View File
@@ -1,326 +1,637 @@
use super::{clash_api, logger::Logger};
use crate::config::*;
#[cfg(target_os = "macos")]
use crate::core::tray::Tray;
use crate::core::{handle, service};
use crate::log_err;
use crate::{config::*, utils::dirs};
use anyhow::{bail, Context, Result};
use crate::module::mihomo::MihomoManager;
use crate::utils::{dirs, help};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{fs, io::Write, sync::Arc, time::Duration};
use sysinfo::{Pid, System};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use std::{path::PathBuf, sync::Arc, time::Duration};
use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex;
use tokio::time::sleep;
#[derive(Debug)]
pub struct CoreManager {
sidecar: Arc<Mutex<Option<CommandChild>>>,
running: Arc<Mutex<bool>>,
}
#[allow(unused)]
use_service_mode: Arc<Mutex<bool>>,
/// 内核运行模式
#[derive(Debug, Clone, serde::Serialize)]
pub enum RunningMode {
/// 服务模式运行
Service,
/// Sidecar模式运行
Sidecar,
/// 未运行
NotRunning,
}
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
CORE_MANAGER.get_or_init(|| CoreManager {
sidecar: Arc::new(Mutex::new(None)),
use_service_mode: Arc::new(Mutex::new(false)),
running: Arc::new(Mutex::new(false)),
})
}
pub fn init(&self) -> Result<()> {
// kill old clash process
let _ = dirs::clash_pid_path()
.and_then(|path| fs::read(path).map(|p| p.to_vec()).context(""))
.and_then(|pid| String::from_utf8_lossy(&pid).parse().context(""))
.map(|pid| {
let mut system = System::new();
system.refresh_all();
if let Some(proc) = system.process(Pid::from_u32(pid)) {
if proc.name().contains("clash") {
log::debug!(target: "app", "kill old clash process");
proc.kill();
}
}
});
tauri::async_runtime::spawn(async {
// 启动clash
log_err!(Self::global().run_core().await);
});
Ok(())
}
/// 检查订阅是否正确
pub fn check_config(&self) -> Result<()> {
let config_path = Config::generate_file(ConfigType::Check)?;
let config_path = dirs::path_to_str(&config_path)?;
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into());
let app_dir = dirs::app_home_dir()?;
let app_dir = dirs::path_to_str(&app_dir)?;
let output = Command::new_sidecar(clash_core)?
.args(["-t", "-d", app_dir, "-f", config_path])
.output()?;
if !output.status.success() {
let error = clash_api::parse_check_output(output.stdout.clone());
let error = match !error.is_empty() {
true => error,
false => output.stdout.clone(),
};
Logger::global().set_log(output.stdout);
bail!("{error}");
}
Ok(())
}
/// 启动核心
pub async fn run_core(&self) -> Result<()> {
let config_path = Config::generate_file(ConfigType::Run)?;
#[allow(unused_mut)]
let mut should_kill = match self.sidecar.lock().take() {
Some(child) => {
log::debug!(target: "app", "stop the core by sidecar");
let _ = child.kill();
true
}
None => false,
};
#[cfg(target_os = "windows")]
if *self.use_service_mode.lock() {
log::debug!(target: "app", "stop the core by service");
log_err!(super::win_service::stop_core_by_service().await);
should_kill = true;
}
// 这里得等一会儿
if should_kill {
sleep(Duration::from_millis(500)).await;
}
#[cfg(target_os = "windows")]
{
use super::win_service;
// 服务模式
let enable = { Config::verge().latest().enable_service_mode };
let enable = enable.unwrap_or(false);
*self.use_service_mode.lock() = enable;
if enable {
// 服务模式启动失败就直接运行sidecar
log::debug!(target: "app", "try to run core in service mode");
match (|| async {
win_service::check_service().await?;
win_service::run_core_by_service(&config_path).await
})()
.await
{
Ok(_) => return Ok(()),
Err(err) => {
// 修改这个值,免得stop出错
*self.use_service_mode.lock() = false;
log::error!(target: "app", "{err}");
}
}
}
}
let app_dir = dirs::app_home_dir()?;
let app_dir = dirs::path_to_str(&app_dir)?;
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into());
let is_clash = clash_core == "clash";
let config_path = dirs::path_to_str(&config_path)?;
let args = match clash_core.as_str() {
"clash-meta" => vec!["-d", app_dir, "-f", config_path],
"clash-meta-alpha" => vec!["-d", app_dir, "-f", config_path],
_ => vec!["-d", app_dir, "-f", config_path],
};
let cmd = Command::new_sidecar(clash_core)?;
let (mut rx, cmd_child) = cmd.args(args).spawn()?;
// 将pid写入文件中
crate::log_err!((|| {
let pid = cmd_child.pid();
let path = dirs::clash_pid_path()?;
fs::File::create(path)
.context("failed to create the pid file")?
.write(format!("{pid}").as_bytes())
.context("failed to write pid to the file")?;
<Result<()>>::Ok(())
})());
let mut sidecar = self.sidecar.lock();
*sidecar = Some(cmd_child);
drop(sidecar);
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => {
if is_clash {
let stdout = clash_api::parse_log(line.clone());
log::info!(target: "app", "[clash]: {stdout}");
} else {
log::info!(target: "app", "[clash]: {line}");
};
Logger::global().set_log(line);
}
CommandEvent::Stderr(err) => {
// let stdout = clash_api::parse_log(err.clone());
log::error!(target: "app", "[clash]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Error(err) => {
log::error!(target: "app", "[clash]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Terminated(_) => {
log::info!(target: "app", "clash core terminated");
let _ = CoreManager::global().recover_core();
break;
}
_ => {}
}
}
});
Ok(())
}
/// 重启内核
pub fn recover_core(&'static self) -> Result<()> {
// 服务模式不管
#[cfg(target_os = "windows")]
if *self.use_service_mode.lock() {
return Ok(());
}
// 清空原来的sidecar值
if let Some(sidecar) = self.sidecar.lock().take() {
let _ = sidecar.kill();
}
tauri::async_runtime::spawn(async move {
// 6秒之后再查看服务是否正常 (时间随便搞的)
// terminated 可能是切换内核 (切换内核已经有500ms的延迟)
sleep(Duration::from_millis(6666)).await;
if self.sidecar.lock().is_none() {
log::info!(target: "app", "recover clash core");
// 重新启动app
if let Err(err) = self.run_core().await {
log::error!(target: "app", "failed to recover clash core");
log::error!(target: "app", "{err}");
let _ = self.recover_core();
}
}
});
pub async fn init(&self) -> Result<()> {
log::trace!("run core start");
// 启动clash
log_err!(Self::global().start_core().await);
log::trace!("run core end");
Ok(())
}
/// 停止核心运行
pub fn stop_core(&self) -> Result<()> {
#[cfg(target_os = "windows")]
if *self.use_service_mode.lock() {
log::debug!(target: "app", "stop the core by service");
tauri::async_runtime::block_on(async move {
log_err!(super::win_service::stop_core_by_service().await);
});
pub async fn stop_core(&self) -> Result<()> {
let mut running = self.running.lock().await;
if !*running {
log::debug!("core is not running");
return Ok(());
}
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
log::debug!(target: "app", "stop the core by sidecar");
let _ = child.kill();
// 关闭tun模式
// Create a JSON object to disable TUN mode
let disable = serde_json::json!({
"tun": {
"enable": false
}
});
log::debug!(target: "app", "disable tun mode");
log_err!(MihomoManager::global().patch_configs(disable).await);
// 服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "stop the core by service");
match service::stop_core_by_service().await {
Ok(_) => {
log::info!(target: "app", "core stopped successfully by service");
}
Err(err) => {
log::warn!(target: "app", "failed to stop core by service: {}", err);
// 服务停止失败,尝试停止可能的sidecar进程
self.stop_sidecar_process();
}
}
} else {
// 如果没有使用服务,尝试停止sidecar进程
self.stop_sidecar_process();
}
*running = false;
Ok(())
}
/// 停止通过sidecar启动的进程
fn stop_sidecar_process(&self) {
if let Some(process) = handle::Handle::global().take_core_process() {
log::info!(target: "app", "stopping core process in sidecar mode");
if let Err(e) = process.kill() {
log::warn!(target: "app", "failed to kill core process: {}", e);
} else {
log::info!(target: "app", "core process stopped successfully");
}
}
}
/// 启动核心
pub async fn start_core(&self) -> Result<()> {
let mut running = self.running.lock().await;
if *running {
log::info!("core is running");
return Ok(());
}
let config_path = Config::generate_file(ConfigType::Run)?;
// 先尝试服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "try to run core in service mode");
match service::run_core_by_service(&config_path).await {
Ok(_) => {
log::info!(target: "app", "core started successfully in service mode");
}
Err(err) => {
// 服务启动失败,尝试sidecar模式
log::warn!(target: "app", "failed to start core in service mode: {}", err);
log::info!(target: "app", "trying to run core in sidecar mode");
self.run_core_by_sidecar(&config_path).await?;
}
}
} else {
// 服务不可用,直接使用sidecar模式
log::info!(target: "app", "service not available, running core in sidecar mode");
self.run_core_by_sidecar(&config_path).await?;
}
// 流量订阅
#[cfg(target_os = "macos")]
log_err!(Tray::global().subscribe_traffic().await);
*running = true;
Ok(())
}
/// 通过sidecar启动内核
async fn run_core_by_sidecar(&self, config_path: &PathBuf) -> Result<()> {
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
log::info!(target: "app", "starting core {} in sidecar mode", clash_core);
let app_handle = handle::Handle::global()
.app_handle()
.ok_or(anyhow::anyhow!("failed to get app handle"))?;
// 获取配置目录
let config_dir = dirs::app_home_dir()?;
let config_path_str = dirs::path_to_str(config_path)?;
// 启动核心进程并转入后台运行
let (_, child) = app_handle
.shell()
.sidecar(clash_core)?
.args(["-d", dirs::path_to_str(&config_dir)?, "-f", config_path_str])
.spawn()?;
// 保存进程ID以便后续管理
handle::Handle::global().set_core_process(child);
// 等待短暂时间确保启动成功
sleep(Duration::from_millis(300)).await;
log::info!(target: "app", "core started in sidecar mode");
Ok(())
}
/// 重启内核
pub async fn restart_core(&self) -> Result<()> {
// 重新启动app
log::info!(target: "app", "restarting core");
self.stop_core().await?;
self.start_core().await?;
log::info!(target: "app", "core restarted successfully");
Ok(())
}
/// 使用默认配置
pub async fn use_default_config(&self, msg_type: &str, msg_content: &str) -> Result<()> {
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
*Config::runtime().draft() = IRuntime {
config: Some(Config::clash().latest().0.clone()),
exists_keys: vec![],
chain_logs: Default::default(),
};
help::save_yaml(
&runtime_path,
&Config::clash().latest().0,
Some("# Clash Verge Runtime"),
)?;
handle::Handle::notice_message(msg_type, msg_content);
Ok(())
}
/// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
const CLASH_CORES: [&str; 2] = ["clash-meta", "clash-meta-alpha"];
const CLASH_CORES: [&str; 2] = ["verge-mihomo", "verge-mihomo-alpha"];
if !CLASH_CORES.contains(&clash_core.as_str()) {
bail!("invalid clash core name \"{clash_core}\"");
}
log::debug!(target: "app", "change core to `{clash_core}`");
log::info!(target: "app", "change core to `{clash_core}`");
// 1. 先更新内核配置(但不应用)
Config::verge().draft().clash_core = Some(clash_core);
// 更新订阅
Config::generate()?;
self.check_config()?;
// 清掉旧日志
Logger::global().clear_log();
match self.run_core().await {
Ok(_) => {
// 2. 使用新内核验证配置
println!("[切换内核] 使用新内核验证配置");
match self.validate_config().await {
Ok((true, _)) => {
println!("[切换内核] 配置验证通过,开始切换内核");
// 3. 验证通过后,应用内核配置并重启
Config::verge().apply();
Config::runtime().apply();
log_err!(Config::verge().latest().save_file());
Ok(())
match self.restart_core().await {
Ok(_) => {
println!("[切换内核] 内核切换成功");
Config::runtime().apply();
Ok(())
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
// 即使使用服务失败,我们也尝试使用sidecar模式启动
log::info!(target: "app", "trying sidecar mode after service failure");
self.start_core().await?;
Config::runtime().apply();
Ok(())
}
}
}
Ok((false, error_msg)) => {
println!("[切换内核] 配置验证失败: {}", error_msg);
// 使用默认配置并继续切换内核
self.use_default_config("config_validate::core_change", &error_msg)
.await?;
Config::verge().apply();
log_err!(Config::verge().latest().save_file());
match self.restart_core().await {
Ok(_) => {
println!("[切换内核] 内核切换成功(使用默认配置)");
Ok(())
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
// 即使使用服务失败,我们也尝试使用sidecar模式启动
log::info!(target: "app", "trying sidecar mode after service failure with default config");
self.start_core().await?;
Ok(())
}
}
}
Err(err) => {
println!("[切换内核] 验证过程发生错误: {}", err);
Config::verge().discard();
Config::runtime().discard();
Err(err)
}
}
}
/// 更新proxies那些
/// 如果涉及端口和外部控制则需要重启
pub async fn update_config(&self) -> Result<()> {
log::debug!(target: "app", "try to update clash config");
/// 内部验证配置文件的实现
async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> {
// 检查程序是否正在退出,如果是则跳过验证
if handle::Handle::global().is_exiting() {
println!("[core配置验证] 应用正在退出,跳过验证");
return Ok((true, String::new()));
}
// 更新订阅
Config::generate()?;
println!("[core配置验证] 开始验证配置文件: {}", config_path);
// 检查订阅是否正常
self.check_config()?;
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
println!("[core配置验证] 使用内核: {}", clash_core);
// 更新运行时订阅
let path = Config::generate_file(ConfigType::Run)?;
let path = dirs::path_to_str(&path)?;
let app_handle = handle::Handle::global().app_handle().unwrap();
let test_dir = dirs::app_home_dir()?.join("test");
let test_dir = dirs::path_to_str(&test_dir)?;
println!("[core配置验证] 测试目录: {}", test_dir);
// 发送请求 发送5次
for i in 0..5 {
match clash_api::put_configs(path).await {
Ok(_) => break,
// 使用子进程运行clash验证配置
println!("[core配置验证] 运行子进程验证配置");
let output = app_handle
.shell()
.sidecar(clash_core)?
.args(["-t", "-d", test_dir, "-f", config_path])
.output()
.await?;
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
// 检查进程退出状态和错误输出
let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"];
let has_error =
!output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
println!("\n[core配置验证] -------- 验证结果 --------");
println!("[core配置验证] 进程退出状态: {:?}", output.status);
if !stderr.is_empty() {
println!("[core配置验证] stderr输出:\n{}", stderr);
}
if !stdout.is_empty() {
println!("[core配置验证] stdout输出:\n{}", stdout);
}
if has_error {
println!("[core配置验证] 发现错误,开始处理错误信息");
let error_msg = if !stdout.is_empty() {
stdout.to_string()
} else if !stderr.is_empty() {
stderr.to_string()
} else if let Some(code) = output.status.code() {
format!("验证进程异常退出,退出码: {}", code)
} else {
"验证进程被终止".to_string()
};
println!("[core配置验证] -------- 验证结束 --------\n");
Ok((false, error_msg)) // 返回错误消息给调用者处理
} else {
println!("[core配置验证] 验证成功");
println!("[core配置验证] -------- 验证结束 --------\n");
Ok((true, String::new()))
}
}
/// 验证运行时配置
pub async fn validate_config(&self) -> Result<(bool, String)> {
let config_path = Config::generate_file(ConfigType::Check)?;
let config_path = dirs::path_to_str(&config_path)?;
self.validate_config_internal(config_path).await
}
/// 验证指定的配置文件
pub async fn validate_config_file(
&self,
config_path: &str,
is_merge_file: Option<bool>,
) -> Result<(bool, String)> {
// 检查程序是否正在退出,如果是则跳过验证
if handle::Handle::global().is_exiting() {
println!("[core配置验证] 应用正在退出,跳过验证");
return Ok((true, String::new()));
}
// 检查文件是否存在
if !std::path::Path::new(config_path).exists() {
let error_msg = format!("File not found: {}", config_path);
//handle::Handle::notice_message("config_validate::file_not_found", &error_msg);
return Ok((false, error_msg));
}
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
if is_merge_file.unwrap_or(false) {
println!(
"[core配置验证] 检测到Merge文件,仅进行语法检查: {}",
config_path
);
return self.validate_file_syntax(config_path).await;
}
// 检查是否为脚本文件
let is_script = if config_path.ends_with(".js") {
true
} else {
match self.is_script_file(config_path) {
Ok(result) => result,
Err(err) => {
if i < 4 {
log::info!(target: "app", "{err}");
} else {
bail!(err);
// 如果无法确定文件类型,尝试使用Clash内核验证
log::warn!(target: "app", "无法确定文件类型: {}, 错误: {}", config_path, err);
return self.validate_config_internal(config_path).await;
}
}
};
if is_script {
log::info!(target: "app", "检测到脚本文件,使用JavaScript验证: {}", config_path);
return self.validate_script_file(config_path).await;
}
// 对YAML配置文件使用Clash内核验证
log::info!(target: "app", "使用Clash内核验证配置文件: {}", config_path);
self.validate_config_internal(config_path).await
}
/// 检查文件是否为脚本文件
fn is_script_file(&self, path: &str) -> Result<bool> {
// 1. 先通过扩展名快速判断
if path.ends_with(".yaml") || path.ends_with(".yml") {
return Ok(false); // YAML文件不是脚本文件
} else if path.ends_with(".js") {
return Ok(true); // JS文件是脚本文件
}
// 2. 读取文件内容
let content = match std::fs::read_to_string(path) {
Ok(content) => content,
Err(err) => {
log::warn!(target: "app", "无法读取文件以检测类型: {}, 错误: {}", path, err);
return Err(anyhow::anyhow!(
"Failed to read file to detect type: {}",
err
));
}
};
// 3. 检查是否存在明显的YAML特征
let has_yaml_features = content.contains(": ")
|| content.contains("#")
|| content.contains("---")
|| content.lines().any(|line| line.trim().starts_with("- "));
// 4. 检查是否存在明显的JS特征
let has_js_features = content.contains("function ")
|| content.contains("const ")
|| content.contains("let ")
|| content.contains("var ")
|| content.contains("//")
|| content.contains("/*")
|| content.contains("*/")
|| content.contains("export ")
|| content.contains("import ");
// 5. 决策逻辑
if has_yaml_features && !has_js_features {
// 只有YAML特征,没有JS特征
return Ok(false);
} else if has_js_features && !has_yaml_features {
// 只有JS特征,没有YAML特征
return Ok(true);
} else if has_yaml_features && has_js_features {
// 两种特征都有,需要更精细判断
// 优先检查是否有明确的JS结构特征
if content.contains("function main")
|| content.contains("module.exports")
|| content.contains("export default")
{
return Ok(true);
}
// 检查冒号后是否有空格(YAML的典型特征)
let yaml_pattern_count = content.lines().filter(|line| line.contains(": ")).count();
if yaml_pattern_count > 2 {
return Ok(false); // 多个键值对格式,更可能是YAML
}
}
// 默认情况:无法确定时,假设为非脚本文件(更安全)
log::debug!(target: "app", "无法确定文件类型,默认当作YAML处理: {}", path);
Ok(false)
}
/// 验证脚本文件语法
async fn validate_script_file(&self, path: &str) -> Result<(bool, String)> {
// 读取脚本内容
let content = match std::fs::read_to_string(path) {
Ok(content) => content,
Err(err) => {
let error_msg = format!("Failed to read script file: {}", err);
log::warn!(target: "app", "脚本语法错误: {}", err);
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
return Ok((false, error_msg));
}
};
log::debug!(target: "app", "验证脚本文件: {}", path);
// 使用boa引擎进行基本语法检查
use boa_engine::{Context, Source};
let mut context = Context::default();
let result = context.eval(Source::from_bytes(&content));
match result {
Ok(_) => {
log::debug!(target: "app", "脚本语法验证通过: {}", path);
// 检查脚本是否包含main函数
if !content.contains("function main")
&& !content.contains("const main")
&& !content.contains("let main")
{
let error_msg = "Script must contain a main function";
log::warn!(target: "app", "脚本缺少main函数: {}", path);
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
return Ok((false, error_msg.to_string()));
}
Ok((true, String::new()))
}
Err(err) => {
let error_msg = format!("Script syntax error: {}", err);
log::warn!(target: "app", "脚本语法错误: {}", err);
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
Ok((false, error_msg))
}
}
}
/// 更新proxies等配置
pub async fn update_config(&self) -> Result<(bool, String)> {
// 检查程序是否正在退出,如果是则跳过完整验证流程
if handle::Handle::global().is_exiting() {
println!("[core配置更新] 应用正在退出,跳过验证");
return Ok((true, String::new()));
}
println!("[core配置更新] 开始更新配置");
// 1. 先生成新的配置内容
println!("[core配置更新] 生成新的配置内容");
Config::generate().await?;
// 2. 生成临时文件并进行验证
println!("[core配置更新] 生成临时配置文件用于验证");
let temp_config = Config::generate_file(ConfigType::Check)?;
let temp_config = dirs::path_to_str(&temp_config)?;
println!("[core配置更新] 临时配置文件路径: {}", temp_config);
// 3. 验证配置
match self.validate_config().await {
Ok((true, _)) => {
println!("[core配置更新] 配置验证通过");
// 4. 验证通过后,生成正式的运行时配置
println!("[core配置更新] 生成运行时配置");
let run_path = Config::generate_file(ConfigType::Run)?;
let run_path = dirs::path_to_str(&run_path)?;
// 5. 应用新配置
println!("[core配置更新] 应用新配置");
for i in 0..3 {
match MihomoManager::global().put_configs_force(run_path).await {
Ok(_) => {
println!("[core配置更新] 配置应用成功");
Config::runtime().apply();
return Ok((true, String::new()));
}
Err(err) => {
if i < 2 {
println!("[core配置更新] 第{}次重试应用配置", i + 1);
log::info!(target: "app", "{err}");
sleep(Duration::from_millis(100)).await;
} else {
println!("[core配置更新] 配置应用失败: {}", err);
Config::runtime().discard();
return Ok((false, err.to_string()));
}
}
}
}
Ok((true, String::new()))
}
Ok((false, error_msg)) => {
println!("[core配置更新] 配置验证失败: {}", error_msg);
Config::runtime().discard();
Ok((false, error_msg))
}
Err(e) => {
println!("[core配置更新] 验证过程发生错误: {}", e);
Config::runtime().discard();
Err(e)
}
}
}
/// 只进行文件语法检查,不进行完整验证
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
println!("[core配置语法检查] 开始检查文件: {}", config_path);
// 读取文件内容
let content = match std::fs::read_to_string(config_path) {
Ok(content) => content,
Err(err) => {
let error_msg = format!("Failed to read file: {}", err);
println!("[core配置语法检查] 无法读取文件: {}", error_msg);
return Ok((false, error_msg));
}
};
// 对YAML文件尝试解析,只检查语法正确性
println!("[core配置语法检查] 进行YAML语法检查");
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
Ok(_) => {
println!("[core配置语法检查] YAML语法检查通过");
Ok((true, String::new()))
}
Err(err) => {
// 使用标准化的前缀,以便错误处理函数能正确识别
let error_msg = format!("YAML syntax error: {}", err);
println!("[core配置语法检查] YAML语法错误: {}", error_msg);
Ok((false, error_msg))
}
}
}
/// 获取当前内核运行模式
pub async fn get_running_mode(&self) -> RunningMode {
let running = self.running.lock().await;
if !*running {
return RunningMode::NotRunning;
}
// 检查服务状态
match service::check_service().await {
Ok(_) => {
// 检查服务是否实际运行核心
match service::is_service_running().await {
Ok(true) => RunningMode::Service,
_ => {
// 服务存在但可能没有运行,检查是否有sidecar进程
if handle::Handle::global().has_core_process() {
RunningMode::Sidecar
} else {
RunningMode::NotRunning
}
}
}
}
sleep(Duration::from_millis(250)).await;
Err(_) => {
// 服务不可用,检查是否有sidecar进程
if handle::Handle::global().has_core_process() {
RunningMode::Sidecar
} else {
RunningMode::NotRunning
}
}
}
Ok(())
}
}

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