2266 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
zhaoyuan
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
downer
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
LiuTianYu
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 e6a5a2b4961dba4e891b1b62d6f35db4ca9ee5ce.
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
MystiPanda
ab306d21f3 Release 1.5.10 2024-03-21 13:47:39 +08:00
MystiPanda
1bd7795851 chore: Change a little 2024-03-21 12:55:22 +08:00
MystiPanda
302c142346 build: Update depends 2024-03-21 11:53:25 +08:00
MystiPanda
7c254c9b45 fix: a little 2024-03-21 11:43:16 +08:00
MystiPanda
b1c458359d feat: Confirm before deletion
#703
2024-03-21 11:39:01 +08:00
MystiPanda
2d925c62f5 feat: Support config redir port and tproxy port 2024-03-21 10:54:56 +08:00
MystiPanda
ef4711987f chore: limit port config
#699
2024-03-20 21:23:10 +08:00
MystiPanda
8910dfaecf fix: Limit icon width
#697
2024-03-20 20:38:45 +08:00
MystiPanda
e8b2e79c7d fix: service logs are not cleared
#695
2024-03-20 20:31:00 +08:00
MystiPanda
333ee1e1f7 docs: Improve the issue template 2024-03-20 18:06:02 +08:00
MystiPanda
ba013bbe96 feat: Optimize Linux tray menu 2024-03-18 11:11:18 +08:00
MystiPanda
636e66fe7a Revert "fix: remove activation policy" 2024-03-18 09:53:48 +08:00
MystiPanda
372a45e667 Release 1.5.9 2024-03-17 11:25:25 +08:00
MystiPanda
ab6b796ce2 fix: drag error
#643
2024-03-16 20:37:39 +08:00
MystiPanda
71e6c02897 fix: drag error 2024-03-16 17:06:59 +08:00
MystiPanda
3edeaa7038 fix: Try to fix touch drag
#456
2024-03-16 10:54:19 +08:00
MystiPanda
f2f75d4015 build: Update to Tauri 1.6.1 2024-03-16 00:03:34 +08:00
MystiPanda
69adade738 ci: update alpha script 2024-03-15 21:52:56 +08:00
MystiPanda
1bd88828e4 ci: remove 2024-03-15 21:42:55 +08:00
MystiPanda
977fcbe6cd feat: Try to support more architecture 2024-03-15 21:14:05 +08:00
MystiPanda
aa76a5c436 refactor: Try to migrate to boa_engine
#634
2024-03-15 19:58:22 +08:00
MystiPanda
ea5fad428d fix: Avoid empty user-agent 2024-03-15 18:27:24 +08:00
MystiPanda
0e1e27b35a feat: Try to cache remote images
#603
2024-03-15 16:43:39 +08:00
screw-hand
d41f67a156 fix: mac setting theme font-famil not working (#632) 2024-03-15 15:17:05 +08:00
MystiPanda
6f10dec808 fix: remove activation policy
#592
2024-03-15 12:44:25 +08:00
xkww3n
9b79da2cdf fix: Set REJECT-DROP policy the same text color as REJECT (#622) 2024-03-14 18:54:26 +08:00
MystiPanda
c11b8519f1 Release 1.5.8 2024-03-13 14:17:06 +08:00
wonfen
b9e23a0b59 Sytle: A little tweak 2024-03-13 14:09:08 +08:00
MystiPanda
75d41b6fe5 feat: Add border-radius for window on linux 2024-03-13 13:58:29 +08:00
Amnesiash
4256590735 update profile ui (#594) 2024-03-13 10:53:39 +08:00
MystiPanda
462fb05ea8 fix: Try to fix #577 again 2024-03-11 22:52:29 +08:00
MystiPanda
812dbfd836 fix: script 2024-03-11 22:10:20 +08:00
MystiPanda
cfca837777 fix: typo 2024-03-11 22:05:56 +08:00
MystiPanda
ac5fb1948a fix: Try to fix #577 2024-03-11 22:03:45 +08:00
MystiPanda
22d8b73625 feat: Allow open devtools 2024-03-11 20:19:21 +08:00
MystiPanda
edde40c298 docs: Update Readme 2024-03-11 18:38:58 +08:00
MystiPanda
50ade92238 Release 1.5.7 2024-03-11 17:09:01 +08:00
MystiPanda
b598d00aef fix: display error 2024-03-11 16:18:39 +08:00
MystiPanda
97bee17e4e chore: Change IP 2024-03-11 16:05:09 +08:00
MystiPanda
6aa5c79332 fix: a little 2024-03-11 15:02:41 +08:00
MystiPanda
7c5ce756f9 fix: Change DNS for MacOS Tun Mode
#568
2024-03-11 14:55:00 +08:00
MystiPanda
43d53a9ffa fix: default value 2024-03-11 13:17:45 +08:00
MystiPanda
e6589bee5b fix: Try to fix touch drag
#456
2024-03-11 12:36:20 +08:00
MystiPanda
9f94cad615 feat: Allow to control whether auto check update 2024-03-11 12:17:46 +08:00
MystiPanda
7778d9f773 style: fix a little 2024-03-10 23:56:04 +08:00
MystiPanda
564bd55147 style: fix styles 2024-03-10 23:47:12 +08:00
MystiPanda
a7ff806522 fix styles 2024-03-10 22:13:25 +08:00
MystiPanda
ac3b074d66 chore: proxy group header height 2024-03-10 21:56:49 +08:00
MystiPanda
da949c2604 chore: adjust proxy group font style 2024-03-10 21:52:40 +08:00
MystiPanda
20a9775dc6 chore: Adjust Profile Style 2024-03-10 21:37:52 +08:00
MystiPanda
8ec19e058f chore: Add Default value 2024-03-10 21:15:22 +08:00
MystiPanda
a5c4549722 style: Adjust icon color 2024-03-10 20:48:13 +08:00
Amnesiash
c7d302aa16 style: Adjust colorful icon (#558)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-10 20:37:00 +08:00
MystiPanda
77ac565ff3 chore: i18n 2024-03-10 20:33:27 +08:00
MystiPanda
2e6f834a50 chore: Update Icon 2024-03-10 20:14:51 +08:00
MystiPanda
5b044cca9e feat: Add option to control menu icon 2024-03-10 19:54:47 +08:00
MystiPanda
2ab5623809 chore: Adjust styles 2024-03-10 13:24:38 +08:00
MystiPanda
2d07f0d77c Revert "chore: Adjust secondary text style (#545)"
This reverts commit 8ba7f9f702.
2024-03-10 13:08:09 +08:00
Amnesiash
8ba7f9f702 chore: Adjust secondary text style (#545) 2024-03-10 12:54:34 +08:00
MystiPanda
460ac7a86b style: Adjust delay fontSize
#544
2024-03-10 12:51:53 +08:00
MystiPanda
f93a1ab3eb docs: update preview 2024-03-10 12:32:08 +08:00
MystiPanda
6b02696aa8 chore: Change Default Font 2024-03-10 12:23:11 +08:00
MystiPanda
25f20d6a85 chore: delete debug output 2024-03-10 11:47:55 +08:00
MystiPanda
8387964bae Release 1.5.6 2024-03-10 11:43:37 +08:00
MystiPanda
926278617f feat: Add default webui
#530
2024-03-10 11:41:22 +08:00
MystiPanda
1933737a0c Adjust styles 2024-03-10 11:21:17 +08:00
MystiPanda
b3e5288bde Adjust styles 2024-03-10 11:12:54 +08:00
wonfen
ed6e966b2f Sytle: UI improvement & Update Readme 2024-03-10 07:00:24 +08:00
MystiPanda
9315fe36b6 fix: fix a little 2024-03-10 00:41:18 +08:00
black23eep
41fbf5ba36 Update layout-traffic.tsx (#528) 2024-03-10 00:35:26 +08:00
MystiPanda
40db481453 chore: update 2024-03-10 00:34:22 +08:00
MystiPanda
1b5e295744 fix: fontSize and some styles 2024-03-10 00:22:22 +08:00
Amnesiash
b42a52f7f6 fix settings.svg (#526) 2024-03-10 00:04:06 +08:00
MystiPanda
8268df84d0 chore: center 2024-03-09 23:38:03 +08:00
Charles
eb6fa5f1f1 tweak(ui): menu icon use svg component (#524) 2024-03-09 23:13:08 +08:00
MystiPanda
025c8856ed fix: img path error 2024-03-09 22:43:53 +08:00
MystiPanda
ed421445e9 Release 1.5.5 2024-03-09 22:02:36 +08:00
Amnesiash
0cda07106b refactor: Upgrade to the new UI (#521)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-09 21:37:21 +08:00
wonfen
f335941b62 UI: change paste icon, delete default profile name 2024-03-09 02:34:33 +08:00
Pylogmon
f0719f8bde feat: Merge Providers (#508) 2024-03-08 11:37:52 +08:00
MystiPanda
27fe28661c feat: Add animation for provider update 2024-03-02 13:52:48 +08:00
MystiPanda
bb13b12de6 chore: fix style 2024-02-28 18:25:50 +08:00
MystiPanda
1117e28b2e feat: Try to support touch drag 2024-02-27 23:41:52 +08:00
MystiPanda
0f48873c25 chore: Remove unnecessary hotkey 2024-02-27 11:18:52 +08:00
MystiPanda
faf61b4af3 fix: Image display failed on Linux 2024-02-26 13:31:51 +08:00
Cyenoch
e29ce93d32 Feat: Provide a switch for allowing invalid certificates (#450) 2024-02-25 16:07:06 +08:00
MystiPanda
38ab68492e chore: change style 2024-02-24 15:39:29 +08:00
MystiPanda
53b1576e5f Release 1.5.4 2024-02-24 14:00:01 +08:00
MystiPanda
2835e79973 feat: Support Open/Close Dashboard Hotkey
#439
2024-02-24 13:39:30 +08:00
MystiPanda
8e4d7e989b feat: Show current proxy for group node
#444
2024-02-24 13:09:53 +08:00
MystiPanda
603c6b826c feat: allow disable group icon 2024-02-24 12:38:17 +08:00
MystiPanda
51bcc77cb3 refactor: Optimize implementation of Custom tray icon 2024-02-24 11:25:22 +08:00
MystiPanda
a30d07b924 feat: Support Custom Tray Icon 2024-02-24 00:52:21 +08:00
MystiPanda
11faa5fe39 Release 1.5.3 2024-02-22 00:23:56 +08:00
MystiPanda
ce8383b26f feat: add reset button 2024-02-22 00:19:45 +08:00
MystiPanda
bd8f07c90a fix: Do not set autolaunch at init
#423 #424
2024-02-21 23:50:50 +08:00
MystiPanda
fee077bebd chore: rm -m arg 2024-02-21 23:38:01 +08:00
MystiPanda
558f4d93ba chore: change default value of dns hijack 2024-02-21 16:40:47 +08:00
MystiPanda
e77d126349 chore: change default value of strict route 2024-02-21 11:32:51 +08:00
MystiPanda
cd8667d6de chore: fix placeholder 2024-02-21 11:13:28 +08:00
MystiPanda
8a2d06d010 chore: Add Translation 2024-02-21 11:06:32 +08:00
MystiPanda
92741fc733 feat: Disable system stack when service mode is turned off 2024-02-21 10:52:03 +08:00
MystiPanda
1cff162649 fix: Config data display error
#417
2024-02-21 10:02:28 +08:00
MystiPanda
f59be465ea Release 1.5.2 2024-02-21 00:04:11 +08:00
MystiPanda
27305317ce chore: Auto Update config.yml 2024-02-20 23:54:02 +08:00
MystiPanda
8378320e50 feat: Support Tun Config (#416) 2024-02-20 23:27:03 +08:00
MystiPanda
86a69952db chore: Update patch file 2024-02-20 18:39:36 +08:00
MystiPanda
e212ebfdb9 fix: Allow program run with administrator to start at startup 2024-02-20 18:35:39 +08:00
MystiPanda
f235125d48 chore: add tooltip for tun mode 2024-02-19 18:10:10 +08:00
MystiPanda
8efa815eb9 feat: Support custom delay timeout (#397) 2024-02-18 11:11:22 +08:00
MystiPanda
e54d42576b build: Try restart windows service after install (#395) 2024-02-18 10:19:54 +08:00
MystiPanda
1e18539862 fix: Merge profile unexpect behavior 2024-02-17 15:58:52 +08:00
MystiPanda
673fdf4721 ci: Update Release Note 2024-02-15 20:05:00 +08:00
MystiPanda
57d6bfba51 ci: change tag 2024-02-15 19:45:37 +08:00
MystiPanda
8fa23aecda chore: update ci script 2024-02-15 19:14:35 +08:00
MystiPanda
f9c10533e8 chore: Change CI Name 2024-02-15 18:45:13 +08:00
MystiPanda
c891c3723e ci: Support alpha package 2024-02-15 18:43:20 +08:00
MystiPanda
a0625ef2e7 chore: remove 32bit package 2024-02-15 18:22:15 +08:00
MystiPanda
b2774abb54 chore: default show proxy detials 2024-02-15 18:17:21 +08:00
wonfen
fa089bbc40 doc: remove 32bit package 2024-02-12 02:25:18 +08:00
MystiPanda
8437d1763f fix: build error 2024-02-11 20:51:00 +08:00
MystiPanda
0532b0f9df Release v1.5.1 2024-02-11 20:32:25 +08:00
MystiPanda
5f8eb853f0 style: clear script 2024-02-11 20:03:14 +08:00
MystiPanda
9125a85382 fix: Custom GLOBAL group display error 2024-02-11 18:27:27 +08:00
MystiPanda
5b3dc44c72 feat: Show proxies count for provider 2024-02-10 13:13:27 +08:00
MystiPanda
ee5147f111 feat: Save window maximize state 2024-02-10 12:51:30 +08:00
MystiPanda
f67137fd5e style: change config name
#350
2024-02-08 10:23:54 +08:00
MystiPanda
b67db4a896 fix: Script profile invalid
#347
2024-02-06 08:55:44 +08:00
MystiPanda
067018828c Release 1.5.0 2024-02-06 00:41:12 +08:00
MystiPanda
e990530ada refactor: Remove clash field filter 2024-02-05 17:28:36 +08:00
wonfen
45f863a72a chore: enable clash field TLS fingerprint 2024-02-04 13:07:25 +08:00
MystiPanda
519febbeb2 fix: Try to fix traffic parse error
#337
2024-02-04 10:24:37 +08:00
wonfen
854b1a4caf chore: update changelog 2024-02-03 16:14:26 +08:00
MystiPanda
08eb95e73a fix: Copy Env Type Select Error 2024-02-02 17:57:56 +08:00
MystiPanda
66f8fbf08c chore: typo 2024-02-02 17:47:28 +08:00
MystiPanda
86a30bbb29 chore: Update Changelog 2024-02-02 17:34:15 +08:00
MystiPanda
966c453c27 fix: prevent_exit 2024-02-02 17:26:31 +08:00
MystiPanda
c7bcaf0193 Release 1.4.11 2024-02-02 16:56:39 +08:00
MystiPanda
0ac91e36a0 fix: exit_app event 2024-02-02 16:32:19 +08:00
MystiPanda
6f1299ce9e chore: fix styles 2024-02-02 16:15:23 +08:00
MystiPanda
83ca29d649 chore: Remove prevent_close 2024-02-02 15:57:03 +08:00
MystiPanda
d0206044dc fix: Fix Nisi Error 2024-02-02 15:53:28 +08:00
ycjcl868
d4623e4175 chore: unused clash core (#284) 2024-01-23 11:22:15 +08:00
MystiPanda
68ef03377d chore: fix style 2024-01-21 13:49:07 +08:00
MystiPanda
d3f9d3d033 revert: some style 2024-01-21 13:49:07 +08:00
wonfen
a6ccd04471 Sytle: UI improvement 2024-01-20 16:05:01 +08:00
Lai Zn
0fd8174e77 fix: Solve the confliction issues on auto updater failure (#273) 2024-01-20 13:03:48 +08:00
MystiPanda
ebc63f479a revert: Support both registry and api for windows sysproxy 2024-01-20 12:16:46 +08:00
MystiPanda
570c39c20a build: Update lock file 2024-01-18 22:34:14 +08:00
MystiPanda
1d57257cf5 Release v1.4.10 2024-01-18 22:26:23 +08:00
MystiPanda
82d7baee0b feat: Add exit button on setting page 2024-01-18 22:19:14 +08:00
MystiPanda
1d91e1f1f7 build: Update Depends 2024-01-18 16:26:38 +08:00
MystiPanda
15e8894614 feat: Support Custom Start Page 2024-01-18 15:37:02 +08:00
Lai Zn
0ae96918e9 feat: Use url path name as fallback subscription name (#255) 2024-01-18 14:36:37 +08:00
MystiPanda
e970880059 feat: Show SubInfo for Proxy Provider
#211
2024-01-18 14:26:57 +08:00
Lai Zn
69ae86aba8 fix: make port change set to system proxy immediately (#256) 2024-01-18 09:37:46 +08:00
MystiPanda
8142e1be49 fix: use nanoid to compatible with old devices 2024-01-18 01:16:39 +08:00
MystiPanda
cbc7612451 feat: Optimizing Provider Support 2024-01-18 01:02:30 +08:00
Kiri
09e6a7b7cc feat: Add support for loong64 (#246) 2024-01-17 22:30:51 +08:00
Lai Zn
082e35668a docs: Add guidelines for windows development (#250) 2024-01-17 19:02:39 +08:00
MystiPanda
c9e78c837b fix: Do not use proxy when test on tun mode 2024-01-17 18:08:09 +08:00
MystiPanda
2d171db672 Release v1.4.9 2024-01-17 15:43:15 +08:00
MystiPanda
a8b11abec8 feat: Support Startup Script 2024-01-17 15:06:16 +08:00
MystiPanda
b08333dccd feat: Support proxy group icon 2024-01-17 13:32:56 +08:00
MystiPanda
98bad48971 fix: Fix connection table sort error
#108
2024-01-17 12:54:54 +08:00
MystiPanda
53375bb536 chore: Update I18n 2024-01-17 11:30:19 +08:00
MystiPanda
88798093e1 fix: csp error 2024-01-17 11:08:14 +08:00
MystiPanda
45a28751af feat: Add Test Page 2024-01-17 11:02:17 +08:00
MystiPanda
1670c44464 fix: fix column width 2024-01-16 19:02:38 +08:00
MystiPanda
a286ac85dc Release v1.4.8 2024-01-16 15:36:04 +08:00
MystiPanda
1606adc0ab feat: Support both registry and api for windows sysproxy 2024-01-16 15:11:53 +08:00
MystiPanda
b4ce557d92 fix: Can not use specify update time when create profile 2024-01-16 10:29:04 +08:00
MystiPanda
2ba7fff8d4 refactor: rm theme blur 2024-01-15 17:13:55 +08:00
MystiPanda
98efc93610 Revert Use Tauri Http Api 2024-01-15 10:18:04 +08:00
MystiPanda
2680507eae Revert Use Tauri Websocket 2024-01-15 10:17:00 +08:00
MystiPanda
df72392ad2 fix: Fix connections sort issue and add total traffic info 2024-01-15 00:46:19 +08:00
MystiPanda
a6acb15a00 refactor: Use Tauri Http API 2024-01-14 19:35:03 +08:00
MystiPanda
ba7242a815 refactor: Use Tauri WebSocket 2024-01-14 17:30:18 +08:00
MystiPanda
99740c1324 fix patch error 2024-01-11 14:47:25 +08:00
MystiPanda
c04b20d1fa Release v1.4.7 2024-01-11 13:19:05 +08:00
MystiPanda
e127067878 feat: Disable updater for portable 2024-01-11 12:44:30 +08:00
MystiPanda
6f03b72368 feat: Support hide group
#214
2024-01-11 12:34:05 +08:00
Morris Li
7bd3ee9340 Fix expired Tauri domain (#222) 2024-01-11 10:17:56 +08:00
MystiPanda
c94db606e5 build: Use old sysproxy 2024-01-10 19:24:14 +08:00
MystiPanda
4d2474226b refactor: cargo clippy 2024-01-10 17:36:35 +08:00
MystiPanda
1ffc4f538b refactor: Optimizing the implementation of Linux URL Scheme registration 2024-01-10 16:34:35 +08:00
MystiPanda
6702ac957b fix: resolve scheme error 2024-01-10 15:37:40 +08:00
MystiPanda
ba42b2e77d fix: linux build error 2024-01-10 15:22:08 +08:00
MystiPanda
fba18ca40a feat: Support URL Scheme for MacOS 2024-01-10 14:04:06 +08:00
MystiPanda
3a2a7a1476 feat: Support URL Scheme for Linux 2024-01-10 13:03:34 +08:00
MystiPanda
3c6d6b90bc build: fix macos build script 2024-01-09 22:49:11 +08:00
MystiPanda
47dc9ee304 fix patch error 2024-01-09 22:25:25 +08:00
MystiPanda
0045bf206c Release v1.4.6 2024-01-09 22:17:29 +08:00
MystiPanda
669a1a6953 feat: Support URL Scheme for Windows
#165
2024-01-09 21:57:06 +08:00
MystiPanda
e28452cc7b feat: Optimize control button style 2024-01-09 16:04:56 +08:00
MystiPanda
5ceac03db1 feat: Add pin button 2024-01-09 15:13:59 +08:00
MystiPanda
96215e5950 fix: Fix some compile error 2024-01-09 14:52:43 +08:00
wonfen
7a030b9224 Style: UI improvement & 1.4.6 ready 2024-01-09 13:57:53 +08:00
wonfen
e743478a4d chore: UI adjustment 2024-01-08 19:32:21 +08:00
MystiPanda
bc29c80d44 chore: Use Latest and compatible core 2023-12-23 12:09:19 +08:00
MystiPanda
82b8a474d7 fix: Cargo clippy 2023-12-21 16:49:21 +08:00
MystiPanda
99851b297d fix: Get filename error
#165
2023-12-19 19:52:13 +08:00
MystiPanda
e3a500e12c fix: portable flag 2023-12-15 21:39:34 +08:00
MystiPanda
378666e3cc chore: update service url 2023-12-15 20:38:17 +08:00
MystiPanda
b65ad1ebd7 fix: user-agent version error 2023-12-15 15:18:01 +08:00
MystiPanda
933a821b5f fix: Subinfo parse error 2023-12-15 11:35:10 +08:00
MystiPanda
159337ddbd Release 1.4.5 2023-12-14 17:40:48 +08:00
MystiPanda
2e25a4333a fix: Window control button icon issue
#136
2023-12-14 14:59:55 +08:00
MystiPanda
64fafb0795 chore: Remove unsafe function 2023-12-14 13:56:51 +08:00
MystiPanda
5927e0bb3b fix: icon size 2023-12-14 13:20:01 +08:00
MystiPanda
e21596db83 feat: Update MacOS tray icon 2023-12-14 13:11:46 +08:00
MystiPanda
8a608c3c3e chore: Optimize service path 2023-12-14 13:03:52 +08:00
MystiPanda
76f9db8516 chore: Optimize upgrade process 2023-12-14 11:24:26 +08:00
MystiPanda
90f4809b7c chore: default enable clash field filter 2023-12-14 10:49:58 +08:00
MystiPanda
3ff1af9fd6 chore: Delete unnecessary drag area 2023-12-13 11:09:19 +08:00
MystiPanda
711dd520f7 chore: Change service path 2023-12-13 10:59:07 +08:00
MystiPanda
99503c836a fix: Save wrong window size 2023-12-12 16:42:19 +08:00
MystiPanda
2c8bc51862 Revert "fix: Change PID file path"
This reverts commit d64bdf02de.
2023-12-12 15:59:00 +08:00
MystiPanda
5e7aa15232 fix: Can't switch env type 2023-12-11 10:51:59 +08:00
MystiPanda
d0761869b6 build: fix update issue 2023-12-10 22:24:46 +08:00
MystiPanda
bb99b89228 build: fix patch 2023-12-10 21:40:57 +08:00
MystiPanda
7b88beb0b5 v1.4.4 2023-12-10 21:36:22 +08:00
MystiPanda
82c630bd0e feat: Support windows aarch64 (#112) 2023-12-10 20:45:27 +08:00
MystiPanda
bb4f11e1b8 feat: Patch for windows aarch64 2023-12-10 19:50:01 +08:00
MystiPanda
3182d20b7c build: Update Depends 2023-12-10 17:14:48 +08:00
MystiPanda
a6f15e2474 chore: Hide script mode for alpha core 2023-12-10 16:31:55 +08:00
MystiPanda
0c3f03680c Support upgrade alpha core 2023-12-10 15:57:10 +08:00
MystiPanda
a66e8dfc9f feat: Support update geodata
#37
2023-12-10 15:22:04 +08:00
zclkkk
556232aac4 chore: Allow user to choose where to install (#110) 2023-12-10 09:50:43 +08:00
MystiPanda
7fcfaadd91 chore: Remove script mode 2023-12-09 17:04:03 +08:00
MystiPanda
18d388e1e2 docs: Update ISSUE_TEMPLATE 2023-12-09 12:00:01 +08:00
MystiPanda
7de6622d74 feat: Support different tray icon for macos 2023-12-09 11:22:02 +08:00
MystiPanda
9af1f90990 feat: Support different tray icon for linux 2023-12-09 11:03:19 +08:00
Pylogmon
15ab43963d feat: Optimize copy environment variable logic (#106) 2023-12-08 22:16:42 +08:00
MystiPanda
6b7465a4b0 perf: Improves window creation speed 2023-12-08 13:10:35 +08:00
MystiPanda
d64bdf02de fix: Change PID file path
#99
2023-12-08 11:59:40 +08:00
MystiPanda
4a2c94993a docs: Update README 2023-12-07 16:49:59 +08:00
MystiPanda
86c318d86b feat: Add AppImage for x86 linux 2023-12-07 16:38:39 +08:00
MystiPanda
bffc1ad41d ci: fix build script 2023-12-07 16:17:34 +08:00
MystiPanda
2a685c116f chore: Fix build error 2023-12-07 16:02:29 +08:00
MystiPanda
0e271d2924 ci: Fix Linux CI Script 2023-12-07 15:57:53 +08:00
MystiPanda
7127c4d590 ci: Fix Linux Build Script 2023-12-07 15:34:49 +08:00
MystiPanda
a82929996b chore: change default port to 7897 2023-12-07 14:52:14 +08:00
MystiPanda
3f01f52c7b refactor: fix depends 2023-12-07 14:44:44 +08:00
MystiPanda
9c94494622 refactor: Remove unnecessary changes 2023-12-07 13:43:53 +08:00
MystiPanda
c8a508f35c Revert "chore: change default port to 7897"
This reverts commit 4ec73ef1c3.
2023-12-07 13:32:49 +08:00
wonfen
4ec73ef1c3 chore: change default port to 7897 2023-12-06 03:37:16 +08:00
MystiPanda
3465b79e5b docs: Update README 2023-12-05 17:02:54 +08:00
MystiPanda
726a0a33ae docs: UPDATELOG 2023-12-05 16:47:42 +08:00
MystiPanda
ba05810d80 Release 1.4.3 2023-12-05 16:28:50 +08:00
MystiPanda
10bb53e7de chore: fix appid 2023-12-05 15:41:13 +08:00
MystiPanda
dd8a083546 refactor: Change app_home to standard locations of app_data 2023-12-05 15:39:44 +08:00
MystiPanda
84131a71c9 fix: Stop core before install update 2023-12-05 15:01:15 +08:00
MystiPanda
4909a11896 docs: Update README 2023-12-05 13:33:23 +08:00
MystiPanda
4069357b81 refactor: Remove page animation 2023-12-05 13:30:55 +08:00
MystiPanda
e6e87bcc40 refactor: Change Tun Icon Color 2023-12-05 13:23:54 +08:00
MystiPanda
7ba22045e8 build: Dependency downgrade 2023-12-05 12:55:20 +08:00
Pylogmon
d4040b61c4 refactor: Change Portable Config Path (#66) 2023-12-05 12:52:26 +08:00
Pylogmon
7534f8fc37 refactor: Hide Clash Field Option when Disable Filter (#63) 2023-12-05 08:34:57 +08:00
MystiPanda
7ced009e92 Update README.md 2023-12-04 14:51:46 +08:00
MystiPanda
cc13c71e40 ci: fix updater 2023-12-04 13:51:46 +08:00
MystiPanda
d2f09209a7 chore: Use Nsis 2023-12-04 13:45:28 +08:00
Pylogmon
a241b04d99 fix: Open File (#56) 2023-12-04 12:15:12 +08:00
Pylogmon
4f35d907cb fix: Get Profile Filename (#54) 2023-12-04 12:15:01 +08:00
MystiPanda
9e50d144a3 chore: change linux meta core to compatible 2023-12-04 11:38:54 +08:00
wonfen
8514bb48c4 chore: change windows meta core to compatible 2023-12-04 06:00:31 +08:00
WhizPanda
3ae633a2a1 fix: alpha core can't display memory 2023-12-03 18:55:38 +08:00
WhizPanda
2799cb9118 style: fix icon size 2023-12-03 18:28:41 +08:00
WhizPanda
d20346b6ac docs: Update README.md 2023-12-03 16:42:49 +08:00
Pylogmon
2f00666a68 style: improve drag icon style (#51) 2023-12-03 16:27:13 +08:00
WhizPanda
80f4570093 ci: Fix Portable Script 2023-12-03 15:36:39 +08:00
wonfen
3f0a2ba48f docs: update readme 2023-12-03 15:25:33 +08:00
WhizPanda
fc90094e8a feat: Support Both Stable and Alpha Version (#47) 2023-12-03 14:26:03 +08:00
wonfen
20d580ade8 release: 1.4.2, tweak UI, fix emoji on mac 2023-12-03 14:01:53 +08:00
WhizPanda
290a024a9e chore: Adjust style 2023-12-02 23:20:04 +08:00
WhizPanda
64a283c3a6 chore: Replace Repo Name 2023-12-02 23:04:04 +08:00
WhizPanda
3e93f0ecbc build: Update Depends 2023-12-02 22:36:04 +08:00
WhizPanda
b12bcc1c49 style: Improve Style 2023-12-02 16:23:53 +08:00
WhizPanda
05d7313dcc feat: Support New Clash Field
#46
2023-12-02 15:18:54 +08:00
WhizPanda
c0b6e549f0 build: Update Cargo Depends 2023-12-01 15:52:49 +08:00
WhizPanda
31f9bf219e build: Update Depends 2023-12-01 14:49:32 +08:00
WhizPanda
4906ca7059 feat: support random mixed port 2023-12-01 12:56:18 +08:00
WhizPanda
b963a7a0e5 fix: Fix Updater Script 2023-12-01 11:19:49 +08:00
WhizPanda
5d6dadda76 Remove unnecessary conditions 2023-12-01 11:11:20 +08:00
Pylogmon
77f69fd223 feat: Add Windows x86 and Linux armv7 Support (#44)
* feat: Add Windows x86 and Linux armv7 Support

* ci: Remove Linux armv7 Support
2023-12-01 11:03:18 +08:00
Pylogmon
7f5052bc87 feat: Use Latest Meta Core mihomo (#41) 2023-12-01 02:44:18 +08:00
Pylogmon
3009c1f4f6 feat: Support cross-compiling to aarch64 (#40)
#19
2023-11-30 22:46:09 +08:00
Pylogmon
5425872bba feat: Support Disable Tray Click Event (#38)
#21
2023-11-30 22:45:02 +08:00
Pylogmon
0759e17295 feat: Set different tray icon on tun mode (#33)
好看!
2023-11-30 01:29:11 +08:00
Pylogmon
56a59d25df feat: Add Download Progress for Updater (#34) 2023-11-30 01:28:46 +08:00
Pylogmon
887f92babe feat: Support Drag to Reorder the Profile (#29)
* feat: Support Drag to Reorder the Profile

* style: Remove unnecessary styles
2023-11-29 08:54:02 +08:00
wonfen
197f942b3f Merge pull request #27 from Pylogmon/emoji-font
feat: Embed Emoji fonts
2023-11-28 10:41:35 +08:00
Pylogmon
a5be7a35e9 feat: Embed Emoji fonts 2023-11-28 10:36:32 +08:00
wonfen
e347675265 fix: Adjust font order 2023-11-28 10:03:01 +08:00
wonfen
02a084d571 chore: update preview gif 2023-11-28 08:49:22 +08:00
wonfen
7fbcc23d94 Merge branch 'main' of github.com:wonfen/clash-verge-rev 2023-11-28 07:53:11 +08:00
wonfen
bda87167a3 update clashmeta core, Imporve UI, merge PR, reset icons, fix CI 2023-11-28 07:49:44 +08:00
wonfen
34f53c287e Merge pull request #24 from Pylogmon/tray
fix: Tray Icon Tooltip is Empty
2023-11-28 01:46:41 +08:00
wonfen
9ab07f37d7 Merge pull request #21 from Pylogmon/tray-event
feat: Config Tray Click Event
2023-11-28 01:46:16 +08:00
Pylogmon
36399b39fb chore: fix style 2023-11-27 20:10:31 +08:00
Pylogmon
1e015287a1 chore: Add Translation 2023-11-27 20:04:47 +08:00
Pylogmon
a590aa8485 fix: Tray Icon Tooltip is Empty 2023-11-27 19:55:42 +08:00
Pylogmon
c0582fde66 chore: Remove Debug Info 2023-11-26 18:49:48 +08:00
Pylogmon
d8d75b4afa feat: Config Tray Click Event 2023-11-26 18:35:21 +08:00
wonfen
9d4942723c fix: portable CI again 2023-11-23 15:01:30 +08:00
wonfen
e5b1ece5c0 chore: change preview gif and fix portable CI 2023-11-23 14:34:11 +08:00
wonfen
1b25bfbf5a fix: CI portable 2023-11-23 13:42:16 +08:00
wonfen
7c64d9f42a Merge pull request #3 from Kuingsmile/main
fix: Downgrade runas to fix service install bug
2023-11-23 11:11:06 +08:00
wonfen
61f9f4ef58 chore: update clash.meta core to 2023.11.23, change win icons. 2023-11-23 11:10:32 +08:00
Kuingsmile
b8eac56213 fix: Downgrade runas to fix service install bug 2023-11-22 17:17:12 -08:00
wonfen
ab5fb5749b Merge pull request #2 from Kuingsmile/main
feat: Update DialogContent width in EditorViewer
2023-11-23 07:46:12 +08:00
Kuingsmile
e0fb9a1b25 feat: Update DialogContent width in EditorViewer
component
2023-11-22 06:49:47 -08:00
wonfen
97e717f646 Merge pull request #1 from Kuingsmile/main
feat: add UWP loopback tools
2023-11-22 16:23:38 +08:00
Kuingsmile
caa82ad1e6 feat: add UWP loopback tools 2023-11-22 00:15:41 -08:00
wonfen
7ec251ea6d chore: UI adjustment, add translation, fix CI 2023-11-22 14:52:14 +08:00
wonfen
675b0c3cca chore: update readme & fix updater issue 2023-11-22 07:31:38 +08:00
wonfen
05e54d4b7f chore: remove old updater CI 2023-11-22 07:06:08 +08:00
wonfen
b4527c90e5 Merge remote-tracking branch 'nyanpasu/main' 2023-11-22 06:48:30 +08:00
wonfen
dbc626734d chore: delete clash core, update CI, change profile name, change URL test link 2023-11-22 02:56:47 +08:00
keiko233
3f2ce3cb80 chore: fix typos 2023-11-16 11:31:06 +08:00
keiko233
0820d6d6fb chore: add updater workflow 2023-11-16 10:59:27 +08:00
keiko233
a8c74e39c2 chore: remove un supported platform
* I don't have the relevant equipment to test with
* May support in future
2023-11-16 10:56:03 +08:00
keiko233
d58c29907d chore: no need for second build 2023-11-16 10:41:28 +08:00
keiko233
2322733427 chore: fix missing assets type 2023-11-16 10:37:26 +08:00
keiko233
d76e8fe85a chore: remove un supported platform 2023-11-16 10:30:25 +08:00
keiko233
0c2e4fe34a chore: fix typos 2023-11-16 10:30:01 +08:00
keiko233
30b6c87a49 docs: add preview gif 2023-11-15 15:17:51 +08:00
keiko233
540221467a chore: clean up workflows 2023-11-15 14:37:10 +08:00
keiko233
d44d331a78 Bump Version 1.4.0 2023-11-15 14:06:29 +08:00
keiko233
595554f18a chore: add release build 2023-11-15 13:52:56 +08:00
keiko233
84d3c3f7eb fix: rust lint 2023-11-15 13:49:06 +08:00
keiko233
890f55c9dc chore: test: use pnpm 2023-11-13 14:49:28 +08:00
keiko233
575a14e1f3 feat: minor tweaks 2023-11-12 00:10:23 +08:00
keiko233
91074bebd6 feat: Nyanpasu Misc 2023-11-12 00:09:32 +08:00
keiko233
3459a16b48 chore: switch updater endpoints 2023-11-12 00:08:23 +08:00
keiko233
88c9b6849f fix: valid with unified-delay & tcp-concurrent 2023-11-11 22:34:30 +08:00
keiko233
d87bb25baf chore: delete current release assets 2023-11-11 22:27:28 +08:00
keiko233
34cb796505 feat: add baseContentIn animation 2023-11-11 19:34:03 +08:00
keiko233
1eb34e0662 feat: add route transition 2023-11-11 18:32:11 +08:00
keiko233
01d631033f feat: Material You! 2023-11-11 17:12:57 +08:00
keiko233
84b2c07340 fix: touchpad scrolling causes blank area to appear 2023-11-11 15:03:41 +08:00
keiko233
a86eeb636d chore: drop upload-artifact & use prerelease 2023-11-11 15:00:54 +08:00
keiko233
417a5a8214 chore: fix: typos
* ↑ baka
2023-11-10 11:50:01 +08:00
keiko233
bcf40dde8c fix: typos 2023-11-10 11:35:38 +08:00
keiko233
cad87484c7 fix: download clash core from backup repo 2023-11-10 11:26:05 +08:00
keiko233
b8ad641ed6 chore: add dev workflow 2023-11-10 10:38:41 +08:00
keiko233
ae8197be8b feat: default disable ipv6 2023-11-10 09:10:15 +08:00
keiko233
eafb0274a7 feat: default enable unified-delay & tcp-concurrent with use meta core 2023-11-10 09:08:17 +08:00
keiko233
71a23a4e02 refactor: copy_clash_env 2023-11-09 10:52:52 +08:00
keiko233
26fd90dfa3 feat: support copy CMD & PowerShell proxy env 2023-11-09 09:58:17 +08:00
keiko233
e393ebede2 fix: use meta Country.mmdb 2023-11-09 09:35:41 +08:00
keiko233
2981bb3f19 feat: default use meta core
* :)
2023-11-09 09:34:03 +08:00
keiko233
6e9f05abb1 feat: update Clash Default bypass addrs
*Enabling TUN will cause the local address to go through the proxy
2023-11-09 09:33:38 +08:00
GyDi
9df1115380 chore: fix check 2023-11-03 16:00:34 +08:00
GyDi
f22e360cbb chore: fix check script 2023-11-03 15:52:36 +08:00
MZhao
67769af6f4 feat: new windows tray icons (#899) 2023-11-03 15:01:46 +08:00
Aromia
b1a9a1d6d9 chore: update download links in README.md (#896) 2023-11-03 11:21:20 +08:00
Goooler
cf0606ecb7 chore: bump github actions (#868)
https://github.com/actions/checkout/releases/tag/v4.0.0
https://github.com/actions/setup-node/releases/tag/v4.0.0
2023-11-02 20:14:05 +08:00
neovali
7287edcd6f chore: add fedora linux install description (#874) 2023-11-02 20:13:24 +08:00
GyDi
e0d26203dd feat: support auto clean log files 2023-11-02 20:12:46 +08:00
GyDi
7e3a85e9da fix: latency url empty 2023-11-01 23:22:30 +08:00
GyDi
5a0fed9c93 feat: increase the concurrency of latency test 2023-11-01 20:52:38 +08:00
GyDi
1f1e743912 fix: change default port 2023-10-31 14:56:19 +08:00
GyDi
b4301ed0d5 fix: csp 2023-10-31 12:00:57 +08:00
GyDi
b5391560fc v1.3.8 2023-10-31 01:29:20 +08:00
GyDi
718989cbcf chore: update log 2023-10-31 01:28:57 +08:00
GyDi
d0aee76962 fix: add default valid key 2023-10-30 17:10:51 +08:00
GyDi
fb08af96bd fix: page null exception, close #821 2023-10-30 00:53:24 +08:00
GyDi
510a0c5e70 feat: adjust the delay display interval and color, close #836 2023-10-29 23:01:05 +08:00
keiko233
89bdc8ec75 chore: update dependencies 2023-10-22 00:44:01 +08:00
keiko233
ae25ade318 chore: tsconfig: skip Lib Check 2023-10-21 23:53:57 +08:00
keiko233
dd5e46a8a7 feat: theme: change color 2023-10-21 17:30:50 +08:00
keiko233
f7218aaa9e chore: update README.md 2023-10-21 17:02:13 +08:00
keiko233
ee79bcfc44 feat: profiles: import btn with loading state 2023-10-21 16:50:46 +08:00
keiko233
2c2c174874 feat: profile-viewer: handleOk with loading state 2023-10-21 16:47:39 +08:00
keiko233
f57c49ce3a feat: base-dialog: okBtn use LoadingButton 2023-10-21 16:47:10 +08:00
keiko233
06121acfac chore: import MUI Lab 2023-10-21 16:43:30 +08:00
keiko233
2078ce7446 chore: Update dependencies 2023-10-21 16:43:16 +08:00
keiko233
49f41abfdb feat: Nyanpasu Misc 2023-10-20 11:52:40 +08:00
keiko233
70fcfe6d6c feat: Theme support modify --background-color 2023-10-20 11:12:49 +08:00
keiko233
b060b4b9bf feat: settings use Grid layout 2023-10-20 10:05:03 +08:00
keiko233
ee2135bfb3 chore: Use your own update endpoints and public keys 2023-10-19 09:37:31 +08:00
keiko233
7daa322441 chore: readme remove ad 2023-10-18 13:38:58 +08:00
GyDi
74252cb66b chore: fix rust lint 2023-10-18 13:38:03 +08:00
GyDi
5fe2be031f chore: fix rust lint 2023-10-11 14:55:20 +08:00
GyDi
2ba3aaba47 chore: fix alpha ci 2023-10-11 14:31:44 +08:00
GyDi
ad8903991c fix: try fix csp 2023-10-11 14:21:56 +08:00
GyDi
3e5624c570 chore: update readme 2023-10-11 00:04:56 +08:00
keiko233
88d3bba300 feat: add Connections Info to ConnectionsPage
*Add Upload Traffic, Download Traffic and Active Connections to ConnectionsPage.
*IConnections uploadTotal and downloadTotal data missing not displayed, add it to ConnectionsPage interface here.
2023-10-10 17:05:31 +08:00
Majokeiko
f5ee6f3537 feat: ClashFieldViewer BaseDialog maxHeight usage percentage (#813)
*The overall interface will be more intuitive when the content is longer.
2023-10-10 14:29:27 +08:00
GyDi
afc77e7adc chore: update readme 2023-10-10 10:25:57 +08:00
GyDi
024f42fce6 chore: update readme 2023-10-10 10:25:01 +08:00
GyDi
8a5f12b97c chore: update clash meta 2023-10-08 21:45:12 +08:00
GyDi
954b21cf39 chore: update readme 2023-09-11 10:24:44 +08:00
GyDi
74d095774d chore: fix updater 2023-09-11 10:16:54 +08:00
GyDi
17a2722e6d chore: fix updater 2023-09-11 10:12:40 +08:00
GyDi
c843bddbfe chore: fix clash map, close #736 2023-09-10 19:06:02 +08:00
GyDi
3f22a49755 v1.3.7 2023-09-10 19:02:10 +08:00
GyDi
7af2ffcebf chore: update log 2023-09-10 19:01:59 +08:00
GyDi
de90c959e0 chore: update auto launch 2023-09-10 18:45:17 +08:00
GyDi
9987dc1eb4 fix: i18n 2023-09-10 15:03:29 +08:00
GyDi
3efd575dd2 feat: add Open Dashboard to the hotkey, close #723 2023-09-10 14:46:03 +08:00
GyDi
f4c7b17a87 feat: add check for updates button, close #766 2023-09-10 14:30:31 +08:00
GyDi
16d80718cb fix: fix page undefined exception, close #770 2023-09-10 13:45:18 +08:00
GyDi
ad228d53b7 feat: add paste and clear icon 2023-09-09 16:52:00 +08:00
Majokeiko
15ee1e531b feat: Subscription URL TextField use multiline (#761)
*Subscription link that are too long can make reading difficult, so use multiline TextField.
2023-09-07 16:14:42 +08:00
GyDi
1c8fb3392a chore: update mmdb 2023-08-28 15:06:13 +08:00
GyDi
8647866a32 fix: set min window size, close #734 2023-08-28 15:00:27 +08:00
GyDi
23351c4f1c chore: change ubuntu ci version 2023-08-28 14:44:09 +08:00
GyDi
1367c304cf chore: update clash 2023-08-28 14:22:50 +08:00
GyDi
26d6bcb074 chore: update release link 2023-08-14 11:11:15 +08:00
GyDi
b0d651ece1 chore: update clash meta 2023-08-14 11:09:19 +08:00
GyDi
b6d50ba6a4 fix: rm debug code 2023-08-12 19:16:20 +08:00
GyDi
b3ab6a9166 v1.3.6 2023-08-12 16:12:03 +08:00
GyDi
f39a5ac9c2 chore: update log 2023-08-12 16:11:50 +08:00
GyDi
38a9a9240d fix: use sudo when pkexec not found 2023-08-12 15:58:37 +08:00
GyDi
241b22a465 chore: update ci 2023-08-12 15:41:26 +08:00
GyDi
741abc0366 feat: show loading when change profile 2023-08-05 22:07:30 +08:00
GyDi
7854775de5 fix: remove div 2023-08-05 21:43:58 +08:00
GyDi
e62eaa6b4b fix: list key 2023-08-05 21:43:05 +08:00
GyDi
b4cce23ef4 feat: support proxy provider update 2023-08-05 21:38:44 +08:00
GyDi
2bcaf90fc8 feat: add repo link 2023-08-05 19:54:59 +08:00
GyDi
96ffbe2f84 feat: support clash meta memory usage display 2023-08-05 19:40:23 +08:00
GyDi
6f5acee1c3 fix: websocket disconnect when window focus 2023-08-05 17:21:15 +08:00
GyDi
54e491d8bf feat: supports show connection detail 2023-08-05 16:52:14 +08:00
GyDi
ab6374e278 chore: update geo data to meta, close #707 2023-08-05 13:42:37 +08:00
GyDi
2fda4c9f67 chore: fix faq 2023-08-05 13:41:30 +08:00
GyDi
5138a45b0f chore: add faq and download link 2023-08-05 13:38:19 +08:00
GyDi
b224d4fa8a chore: add promotion 2023-08-05 12:57:48 +08:00
whitemirror33
a552e44483 feat: update connection table with wider process column and click to show full detail (#696) 2023-08-04 14:36:28 +08:00
GyDi
0cf3bba118 feat: more trace logs 2023-08-04 14:15:15 +08:00
Andrei Shevchuk
2c48ea3508 feat: Add Russian Language (#697)
* Add Russian Language

* Add Russian support

* Minor update

* Update Russian translation
2023-08-03 11:07:58 +08:00
GyDi
b9b6212b75 fix: try fix undefined error 2023-07-28 09:26:06 +08:00
GyDi
b978aaec21 chore: alpha ci 2023-07-26 17:07:20 +08:00
GyDi
af704681d9 feat: center window when out of monitor 2023-07-24 20:55:26 +08:00
GyDi
1443ddfe6c chore: fix test ci 2023-07-24 09:59:13 +08:00
GyDi
54457a3e1b chore: fix test ci 2023-07-24 09:47:05 +08:00
GyDi
bf180e6a2c chore: test ci 2023-07-24 09:28:54 +08:00
GyDi
864a5820c9 chore: test ci 2023-07-24 09:13:19 +08:00
GyDi
4d3ca49c3f v1.3.5 2023-07-23 13:31:51 +08:00
GyDi
c49c3cf7f0 chore: update log 2023-07-23 13:31:34 +08:00
GyDi
5d5ab57469 fix: blurry tray icon in Windows 2023-07-23 13:25:54 +08:00
GyDi
31978d8de0 chore: update clash core 2023-07-23 13:11:35 +08:00
GyDi
e8eb68bf24 chore: fix check script 2023-07-23 13:11:17 +08:00
GyDi
9ea08f4fed chore: update issue templates 2023-07-22 23:04:13 +08:00
GyDi
fe078a5c5b v1.3.4 2023-07-22 20:38:08 +08:00
GyDi
61933954f3 chore: update log 2023-07-22 20:37:22 +08:00
GyDi
4c243638cb fix: enable context menu in editable element 2023-07-22 17:21:04 +08:00
GyDi
02ba04b5d8 feat: support copy environment variable 2023-07-22 15:35:32 +08:00
GyDi
4f158a4829 fix: save window size and pos in Windows 2023-07-22 13:13:16 +08:00
GyDi
177a22df59 feat: save window size and position 2023-07-22 10:58:16 +08:00
GyDi
6b0ca2966e feat: app log level add silent 2023-07-22 09:25:54 +08:00
GyDi
aadfaf7150 feat: overwrite resource file according to file modified 2023-07-22 09:18:54 +08:00
GyDi
b307b9a66b feat: support app log level settings 2023-07-22 08:53:37 +08:00
Kimiblock Moe
6c1ab6002d feat: Use polkit to elevate permission instaed of sudo (#678) 2023-07-21 23:05:33 +08:00
GyDi
9638eefc91 chore: update check script 2023-07-11 13:25:55 +08:00
GyDi
9e9c4ad587 fix: optimize traffic graph high CPU usage when hidden 2023-07-10 23:44:09 +08:00
GyDi
ce231431b9 fix: remove fallback group select status, close #659 2023-07-10 23:16:19 +08:00
GyDi
06e1e14e02 chore: update clash 2023-07-10 13:36:17 +08:00
GyDi
416e7884f5 feat: add unified-delay field 2023-06-30 13:58:51 +08:00
GyDi
d579222007 v1.3.3 2023-06-30 09:38:33 +08:00
GyDi
30243c84cd chore: update log 2023-06-30 09:35:49 +08:00
GyDi
3557a77645 fix: error boundary with key 2023-06-30 09:17:59 +08:00
GyDi
97be28638b chore: update clash meta 2023-06-30 09:02:26 +08:00
GyDi
aba0826c38 fix: connections is null 2023-06-30 09:02:17 +08:00
GyDi
f032228d0e fix: font family not works in some interfaces, close #639 2023-06-29 14:21:14 +08:00
GyDi
6cf174c5ed fix: encodeURIComponent secret 2023-06-29 14:15:57 +08:00
GyDi
c2109d245f chore: update clash & clash meta 2023-06-08 13:52:40 +08:00
GyDi
6a9745171e feat: add error boundary to the app root 2023-06-08 13:50:45 +08:00
GyDi
f9a68e8b23 fix: encode controller secret, close #601 2023-06-08 13:48:58 +08:00
GyDi
6e391df5ee fix: linux not change icon 2023-05-28 18:14:11 +08:00
GyDi
f5edca94d3 fix: try fix blank error 2023-05-28 17:35:00 +08:00
GyDi
60046abec3 fix: close all connections when change mode 2023-05-28 17:07:39 +08:00
GyDi
cafc2060b8 fix: macos not change icon 2023-05-28 16:46:17 +08:00
GyDi
b1f45752cf chore: update deps 2023-05-28 16:45:43 +08:00
GyDi
ed17551170 chore: update clash 2023-05-28 16:44:07 +08:00
w568w
ef5adab638 feat: show tray icon variants in different status (#537) 2023-05-28 10:55:39 +08:00
GyDi
fb653ff99d fix: error message null 2023-05-19 10:53:11 +08:00
GyDi
78fc47a9c4 chore: update gh proxy 2023-05-19 09:44:31 +08:00
GyDi
2a124cea61 v1.3.2 2023-05-19 00:57:22 +08:00
GyDi
4c7cc563dc chore: update log 2023-05-19 00:57:10 +08:00
GyDi
6114af4f93 fix: profile data undefined error, close #566 2023-05-18 20:02:46 +08:00
GyDi
8c31629655 chore: update clash core 2023-05-05 20:45:52 +08:00
GyDi
03c8a8edb2 chore: update deps 2023-05-05 20:27:46 +08:00
yettera765
3eeaee154f fix: import url error (#543)
use rustls instead of depending user's system tls
2023-05-05 12:08:08 +08:00
GyDi
8cf8fa7c80 v1.3.1 2023-04-11 10:04:55 +08:00
GyDi
6b4f6fc71e chore: update log 2023-04-11 10:04:28 +08:00
Tatius Titus
30c2680b6f chore: Upgrade to React 18 (#495)
* chore: Upgrade to React 18

* runfix: Add children type to FC components

* chore: Remove @types/react
2023-04-07 12:59:44 +08:00
Mr-Spade
fb7b1800cc fix: linux DEFAULT_BYPASS (#503) 2023-04-07 12:47:13 +08:00
GyDi
ff573bf377 chore: update deps 2023-04-02 11:20:18 +08:00
GyDi
0a33bb861e fix: open file with vscode 2023-04-02 11:19:48 +08:00
GyDi
728756289b chore: update deps 2023-04-02 10:09:14 +08:00
GyDi
56ccd3a0ac chore: update clash meta 2023-04-02 09:43:32 +08:00
Tatius Titus
66f3f0ba07 fix: Do not render div as a descendant of p (#494) 2023-04-02 09:37:09 +08:00
Srinivas Gowda
af5e0d589e chore: update clash core version (#476) 2023-03-27 10:59:27 +08:00
GyDi
533dc99e7d fix: use replace instead 2023-03-17 08:37:45 +08:00
GyDi
fc5ca965ba fix: escape path space 2023-03-17 08:36:19 +08:00
John Smith
9c4a46bcdb fix: escape the space in path (#451) 2023-03-17 08:20:35 +08:00
GyDi
52658886e7 fix: add target os linux 2023-03-16 23:57:34 +08:00
GyDi
8174ab7616 chore: fix ci 2023-03-16 23:54:14 +08:00
GyDi
2b6acedae1 fix: appimage path unwrap panic 2023-03-16 23:45:48 +08:00
GyDi
d00fe9c5f4 v1.3.0 2023-03-16 21:33:51 +08:00
GyDi
88aa270728 chore: update log 2023-03-16 21:33:14 +08:00
GyDi
4ae409c7f4 feat: auto restart core after grand permission 2023-03-16 21:32:39 +08:00
GyDi
9a29c9abdd feat: add restart core button 2023-03-16 20:56:37 +08:00
GyDi
66d93ea037 fix: remove esc key listener in macOS 2023-03-16 20:42:45 +08:00
GyDi
6c0066dbfb feat: support update all profiles 2023-03-16 20:32:41 +08:00
GyDi
4fde644733 chore: rm file 2023-03-16 20:31:48 +08:00
GyDi
e7841c60df fix: adjust style 2023-03-16 19:32:59 +08:00
hybo
94f647b24a chore: evaluate error context lazily (#447) 2023-03-16 17:06:52 +08:00
GyDi
630249d22a fix: adjust swr option 2023-03-16 17:03:12 +08:00
GyDi
db99b4cb54 fix: infinite retry when websocket error 2023-03-16 17:03:00 +08:00
GyDi
c77db23586 fix: type error 2023-03-16 13:51:46 +08:00
GyDi
daf66bcec4 chore: update deps 2023-03-16 13:42:27 +08:00
GyDi
8caf36349f chore: fix ci 2023-03-16 13:18:05 +08:00
GyDi
6934de58e5 chore: fix ci 2023-03-16 13:16:30 +08:00
GyDi
54a5007c01 feat: support to grant permission to clash core 2023-03-16 11:16:54 +08:00
GyDi
e25a455698 fix: do not parse log except the clash core 2023-03-16 10:34:28 +08:00
GyDi
ab429dfeb6 feat: support clash fields filter in ui 2023-03-15 08:43:24 +08:00
GyDi
c5289dc0e8 fix: field sort for filter 2023-03-15 08:43:03 +08:00
boatrainlsz
d191877002 chore: update README (#435) 2023-03-07 22:55:38 +08:00
GyDi
4d979160c2 chore: update deps 2023-03-07 00:10:49 +08:00
GyDi
d00e8f6e19 chore: update clash 2023-03-06 23:35:30 +08:00
GyDi
91b77e5237 fix: add meta fields 2023-02-18 00:46:03 +08:00
GyDi
5b8c246d53 chore: update clash 2023-02-18 00:09:40 +08:00
GyDi
b374b9b91c feat: open dir on the tray 2023-02-17 00:15:21 +08:00
GyDi
403717117e fix: runtime config user select 2023-02-17 00:00:23 +08:00
GyDi
027295d995 feat: support to disable clash fields filter 2023-02-16 23:52:55 +08:00
GyDi
c9b7eccbc1 fix: app_handle as_ref 2023-02-15 17:25:35 +08:00
GyDi
2b6d9348cd fix: use crate 2023-02-11 23:31:55 +08:00
GyDi
692f8c8454 fix: appimage auto launch, close #403 2023-02-11 23:19:08 +08:00
inRm3D
6783355c4d chore: add openssl depend (#395) 2023-02-02 10:54:15 +08:00
GyDi
fb9cca1e99 v1.2.3 2023-02-01 22:11:34 +08:00
GyDi
eb770ede1a chore: update log 2023-02-01 22:11:06 +08:00
GyDi
2643e853af chore: aarch script 2023-02-01 21:41:15 +08:00
GyDi
b79456e91b chore: update clash 2023-01-30 20:50:08 +08:00
GyDi
ac66c086f8 chore: meta ci 2023-01-30 20:40:57 +08:00
GyDi
ebccf401dd chore: fix ci 2023-01-22 13:31:34 +08:00
GyDi
66494845b7 chore: fix ci 2023-01-19 18:02:08 +08:00
GyDi
e6c36ad602 chore: alpha ci 2023-01-18 23:39:27 +08:00
inRm3D
26379182db chore: try fix missing libssl3 (#378)
* Update ci.yml

* use package from source
2023-01-18 23:36:18 +08:00
GyDi
bba03d14d4 fix: compatible with UTF8 BOM, close #283 2023-01-17 19:51:02 +08:00
GyDi
23b728a762 feat: adjust macOS window style 2023-01-16 22:57:53 +08:00
GyDi
819c5207d2 fix: use selected proxy after profile changed 2023-01-15 21:33:03 +08:00
GyDi
311358544e fix: error log 2023-01-15 19:11:51 +08:00
GyDi
4480ecc96d chore: ci 2023-01-14 22:22:18 +08:00
GyDi
6c5f70a205 v1.2.2 2023-01-14 22:01:28 +08:00
GyDi
99adfb4a9e chore: update log 2023-01-14 21:56:27 +08:00
GyDi
7909cf4067 fix: adjust fields order 2023-01-14 14:57:55 +08:00
GyDi
780ab20aeb fix: add meta fields 2023-01-14 14:51:29 +08:00
GyDi
73119bb7c5 chore: ci 2023-01-14 12:20:17 +08:00
GyDi
f20f0f064e chore: update clash meta 2023-01-14 12:16:09 +08:00
GyDi
1b44ae098c fix: add os platform value 2023-01-14 12:07:31 +08:00
GyDi
453c230716 feat: recover core after panic, close #353 2023-01-14 11:45:47 +08:00
GyDi
439d885ee1 chore: allow unused 2023-01-13 23:11:48 +08:00
GyDi
43dee3ef76 fix: reconnect traffic websocket 2023-01-13 23:02:45 +08:00
GyDi
c71ba6ff8d chore: update deps 2023-01-13 21:29:21 +08:00
GyDi
fb7a36eb73 feat: use decorations in Linux, close #354 2023-01-12 00:42:33 +08:00
GyDi
e7f294a065 fix: parse bytes precision, close #334 2023-01-11 14:05:49 +08:00
GyDi
d5037f180e fix: trigger new profile dialog, close #356 2023-01-11 13:45:16 +08:00
GyDi
e90158809a fix: parse log cause panic 2023-01-11 13:30:14 +08:00
GyDi
0cb802ed9a chore: update clash meta 2023-01-09 21:55:03 +08:00
GyDi
d0b47204f4 v1.2.1 2022-12-23 22:44:27 +08:00
GyDi
351cb391e5 chore: update log 2022-12-23 22:44:00 +08:00
GyDi
051be927cd fix: avoid setting login item repeatedly, close #326 2022-12-23 22:39:27 +08:00
GyDi
8bad2c2113 fix: adjust code 2022-12-15 21:54:48 +08:00
GyDi
2bcf6fb3eb fix: adjust delay check concurrency 2022-12-15 12:23:57 +08:00
GyDi
d1ba0ed2b2 chore: adjust code 2022-12-15 12:22:20 +08:00
GyDi
6e421e60c5 fix: change default column to auto 2022-12-14 16:56:33 +08:00
GyDi
8385050804 fix: change default app version 2022-12-14 16:15:53 +08:00
GyDi
bfe4f08232 fix: adjust rule ui 2022-12-14 15:59:41 +08:00
GyDi
132f914b0d fix: adjust log ui 2022-12-14 15:49:05 +08:00
GyDi
97d82b03ab fix: keep delay data 2022-12-14 15:29:02 +08:00
GyDi
f06fa3f9b7 fix: use list item button 2022-12-14 15:16:49 +08:00
GyDi
6337788a22 fix: proxy item style 2022-12-14 15:15:44 +08:00
GyDi
024db4358b feat: auto proxy layout column 2022-12-14 15:07:51 +08:00
GyDi
173f35487e chore: fix ci 2022-12-13 18:13:28 +08:00
GyDi
74cbe82dd1 chore: fix ci 2022-12-13 18:11:43 +08:00
GyDi
a8c30d30a9 chore: fix ci 2022-12-13 18:02:23 +08:00
GyDi
1b7a52d5af chore: alpha ci 2022-12-13 17:44:46 +08:00
GyDi
e5109789bf chore: alpha ci 2022-12-13 17:41:53 +08:00
GyDi
4d2b35e09d feat: support to change proxy layout column 2022-12-13 17:34:39 +08:00
GyDi
5c5177ec57 feat: support to open core dir 2022-12-13 00:44:24 +08:00
GyDi
d93b00cd15 chore: update deps 2022-12-12 00:00:45 +08:00
MoeShin
39ade59174 fix: Virtuoso no work in legacy browsers (#318) 2022-12-08 10:47:42 +08:00
GyDi
0309c815b9 chore: update deps 2022-12-04 20:54:43 +08:00
GyDi
ce613098db fix: adjust ui 2022-12-04 00:45:39 +08:00
GyDi
ab34044196 feat: profile page ui 2022-11-28 22:29:58 +08:00
GyDi
17f724748f chore: deps 2022-11-28 22:27:23 +08:00
GyDi
f3a917b5e7 chore: update clash version 2022-11-26 13:25:52 +08:00
GyDi
376011ea08 fix: refresh websocket 2022-11-25 22:22:57 +08:00
GyDi
2ce7624c14 fix: adjust ui 2022-11-24 18:24:34 +08:00
GyDi
c62dddd5b9 feat: save some fields in the runtime config, close #292 2022-11-24 17:52:25 +08:00
GyDi
490ba9f140 chore: alpha ci 2022-11-24 11:14:03 +08:00
GyDi
f76890cc56 fix: parse bytes base 1024 2022-11-24 11:11:31 +08:00
GyDi
e1c8f1fed9 fix: add clash fields 2022-11-24 10:46:21 +08:00
GyDi
6e19a4ab8b chore: ci 2022-11-24 10:41:03 +08:00
GyDi
b156523a7f chore: ci 2022-11-24 10:35:22 +08:00
GyDi
d20b745ae5 fix: direct mode hide proxies 2022-11-24 10:35:09 +08:00
GyDi
c51e9e6b2c feat: add meta feature 2022-11-24 10:26:41 +08:00
GyDi
1a31fa9067 fix: profile can not edit 2022-11-24 10:26:25 +08:00
GyDi
558b8499af chore: ci 2022-11-23 23:46:32 +08:00
GyDi
986c162988 v1.2.0 2022-11-23 23:15:39 +08:00
GyDi
770d5cd11c chore: ci 2022-11-23 23:15:20 +08:00
GyDi
446d2ab3af chore: update log 2022-11-23 23:13:44 +08:00
GyDi
2f9bf7f063 feat: display proxy group type 2022-11-23 18:27:57 +08:00
GyDi
72ff9c0964 chore: rm dead code 2022-11-23 17:45:49 +08:00
GyDi
33b1a11d85 fix: parse logger time 2022-11-23 17:45:43 +08:00
GyDi
06dabf1e4e fix: adjust service mode ui 2022-11-23 17:45:22 +08:00
GyDi
28d3691e0b feat: add use clash hook 2022-11-23 17:44:40 +08:00
GyDi
ffa21fbfd2 fix: adjust style 2022-11-23 17:42:01 +08:00
GyDi
db028665fd fix: check hotkey and optimize hotkey input, close #287 2022-11-23 17:30:19 +08:00
GyDi
6bc83d9f27 fix: mutex dead lock 2022-11-23 16:04:25 +08:00
GyDi
790d832155 fix: adjust item ui 2022-11-23 15:29:42 +08:00
GyDi
f477cecdeb fix: regenerate config before change core 2022-11-23 09:57:21 +08:00
GyDi
e031389021 fix: close connections when profile change 2022-11-23 00:20:57 +08:00
GyDi
e00f826eb8 fix: lint 2022-11-22 23:02:18 +08:00
GyDi
24f4e8ab99 chore: fix check script 2022-11-22 23:01:04 +08:00
GyDi
1550d528bd fix: windows service mode 2022-11-22 22:01:34 +08:00
GyDi
40c041031e fix: init config file 2022-11-22 22:01:19 +08:00
GyDi
3e555ec9f1 chore: fix mmdb url 2022-11-22 21:15:45 +08:00
GyDi
5098f14aab fix: service mode error and fallback to sidecar 2022-11-22 20:47:21 +08:00
GyDi
a355a9c85e fix: service mode viewer ui 2022-11-22 20:45:17 +08:00
GyDi
e7db2a8573 chore: update check script 2022-11-22 20:44:44 +08:00
GyDi
9b18bd0b48 fix: create theme error, close #294 2022-11-22 16:01:33 +08:00
GyDi
f95ddd594e feat: guard the mixed-port and external-controller 2022-11-22 15:45:17 +08:00
GyDi
fe8168784f feat: adjust builtin script and support meta guard script 2022-11-22 12:00:48 +08:00
MoeShin
4046f143f6 fix: matchMedia().addEventListener #258 (#296) 2022-11-22 09:25:39 +08:00
GyDi
e4e16999c8 chore: alpha release add portable 2022-11-22 00:16:30 +08:00
GyDi
10f3ba4ff4 chore: alpha ci 2022-11-21 23:19:25 +08:00
GyDi
3cd2be5081 fix: check config 2022-11-21 23:11:56 +08:00
GyDi
c9359978f9 fix: show global when no rule groups 2022-11-21 23:06:32 +08:00
GyDi
781c67b31a fix: service viewer ref 2022-11-21 23:02:48 +08:00
GyDi
020bd129fb fix: service ref error 2022-11-21 22:33:06 +08:00
GyDi
8086b6d78c feat: disable script mode when use clash meta 2022-11-21 22:28:57 +08:00
GyDi
48e14b36b8 feat: check config when change core 2022-11-21 22:27:55 +08:00
GyDi
b3c1c56579 fix: group proxies render list is null 2022-11-21 22:10:24 +08:00
GyDi
bd0e932910 feat: support builtin script for enhanced mode 2022-11-21 21:05:00 +08:00
GyDi
525e5f88ae chore: update ci 2022-11-20 23:17:05 +08:00
GyDi
005eeb0e0b chore: update ci node version 2022-11-20 23:14:43 +08:00
GyDi
d21bb015e8 fix: pretty bytes 2022-11-20 23:08:30 +08:00
GyDi
7338838b0e feat: adjust profiles page ui 2022-11-20 22:37:34 +08:00
GyDi
8bb4803ff9 refactor: adjust base components export 2022-11-20 22:03:55 +08:00
GyDi
892b919cf3 refactor: adjust setting dialog component 2022-11-20 21:48:39 +08:00
GyDi
572d81ecef fix: use verge hook 2022-11-20 20:12:58 +08:00
GyDi
a4ce7a4037 feat: optimize proxy page ui 2022-11-20 19:46:16 +08:00
GyDi
6eafb15cf9 chore: adjust type 2022-11-19 17:22:29 +08:00
GyDi
e19fe5ce1c fix: adjust notice 2022-11-19 01:24:07 +08:00
GyDi
9d2017e598 feat: add error boundary 2022-11-19 01:22:19 +08:00
GyDi
f33c419ed9 chore: rm polyfill 2022-11-19 01:22:00 +08:00
GyDi
f425fbaf9d feat: adjust clash log 2022-11-18 22:08:06 +08:00
GyDi
bcc5ec897a fix: windows issue 2022-11-18 20:15:34 +08:00
GyDi
f5f2fe3472 fix: change dev log level 2022-11-18 18:37:29 +08:00
GyDi
b7c3863882 fix: patch clash config 2022-11-18 18:37:17 +08:00
GyDi
d759f48ee8 fix: cmds params 2022-11-18 18:26:55 +08:00
GyDi
3a37075e71 Merge branch 'refactor' 2022-11-18 18:21:49 +08:00
GyDi
58366c0b87 chore: rm code 2022-11-18 18:19:26 +08:00
GyDi
2667ed13f1 refactor: done 2022-11-18 18:18:41 +08:00
GyDi
34daffbc96 refactor: adjust all path methods and reduce unwrap 2022-11-18 10:26:39 +08:00
GyDi
be81cd72af fix: adjust singleton detect 2022-11-18 08:24:27 +08:00
GyDi
4ae00714d2 fix: change template 2022-11-18 08:04:26 +08:00
GyDi
f24cbb6692 refactor: rm code 2022-11-17 23:21:13 +08:00
GyDi
5a35c5b928 refactor: fix 2022-11-17 22:53:41 +08:00
GyDi
1880da6351 refactor: rm dead code 2022-11-17 22:52:22 +08:00
GyDi
df93cb103c refactor: for windows 2022-11-17 20:19:40 +08:00
GyDi
63b474a32c refactor: wip 2022-11-17 17:07:13 +08:00
GyDi
abdbf158d1 refactor: wip 2022-11-16 01:26:41 +08:00
GyDi
ee68d80d0a refactor: wip 2022-11-15 01:33:50 +08:00
GyDi
c8e6f3a627 refactor: rm update item block_on 2022-11-14 23:07:51 +08:00
GyDi
dc941575fe refactor: fix 2022-11-14 22:50:47 +08:00
GyDi
e64103e5f2 fix: copy resource file 2022-11-14 21:11:42 +08:00
GyDi
f0ab03a9fb chore: tmpl add clash core 2022-11-14 21:10:29 +08:00
GyDi
09965f1cc6 feat: add draft 2022-11-14 19:31:22 +08:00
GyDi
d2852bb34a refactor: fix 2022-11-14 01:45:58 +08:00
GyDi
b03c52a501 refactor: wip 2022-11-14 01:26:33 +08:00
GyDi
fd6633f536 fix: MediaQueryList addEventListener polyfill 2022-11-13 10:27:26 +08:00
GyDi
320ac81f48 chore: fix ci 2022-11-12 17:07:47 +08:00
GyDi
70bcd2428f chore: alpha ci 2022-11-12 17:06:26 +08:00
GyDi
71f5ada0a3 chore: lock version 2022-11-12 17:06:18 +08:00
GyDi
aab5141404 chore: fix ci 2022-11-12 16:54:57 +08:00
GyDi
ff6b119f27 chore: alpha ci 2022-11-12 16:47:27 +08:00
GyDi
7ef4b7eeb8 fix: change default tun dns-hijack 2022-11-12 11:48:02 +08:00
GyDi
4668be6e24 chore: format rust code 2022-11-12 11:37:23 +08:00
GyDi
a211fc7c97 fix: something 2022-11-11 22:45:32 +08:00
GyDi
54af0b675d chore: update deps 2022-11-11 21:28:34 +08:00
GyDi
8c8171e774 feat: change default latency test url 2022-11-11 01:24:18 +08:00
GyDi
0bb1790206 fix: provider proxy sort by delay 2022-11-11 01:21:23 +08:00
GyDi
45fc84d8be chore: rm dead code 2022-11-10 22:58:46 +08:00
GyDi
f7500f4cad chore: clash meta compatible and geosite.dat 2022-11-10 22:58:34 +08:00
GyDi
0cfd718d8a feat: auto close connection when proxy changed 2022-11-10 01:27:05 +08:00
GyDi
5f486d0f51 fix: profile item menu ui dense 2022-11-09 15:53:42 +08:00
GyDi
6e5a2f85a1 fix: disable auto scroll to proxy 2022-11-09 11:44:09 +08:00
GyDi
e66a89208d feat: support to change external controller 2022-11-06 23:23:26 +08:00
GyDi
7f65c501c6 feat: add sub-rules 2022-11-05 15:50:06 +08:00
GyDi
22c2382765 feat(macOS): support cmd+w and cmd+q 2022-11-04 00:51:46 +08:00
GyDi
38e1a4febf chore: update clash meta 2022-11-04 00:36:32 +08:00
GyDi
8db554b377 chore: fix ci 2022-11-03 01:34:23 +08:00
GyDi
5f5cc55331 chore: compatible ci 2022-11-03 01:32:16 +08:00
GyDi
2f8b39186f chore: update ci 2022-11-03 01:15:50 +08:00
GyDi
a3a724e2e6 chore: test ci 2022-11-03 00:51:49 +08:00
GyDi
73235c8699 chore: test ci 2022-11-03 00:48:27 +08:00
GyDi
7e5999e862 chore: add x 2022-11-02 00:57:27 +08:00
GyDi
afa244dcb0 v1.1.2 2022-11-02 00:54:51 +08:00
GyDi
4ea5bb2390 fix: check remote profile 2022-11-02 00:51:25 +08:00
GyDi
e545d552f6 chore: update log 2022-11-02 00:44:45 +08:00
GyDi
56fe7b3596 feat: add version on tray 2022-11-01 23:29:59 +08:00
GyDi
eb28ec866a feat: add animation 2022-10-29 23:36:10 +08:00
GyDi
4649454282 fix: remove smoother 2022-10-29 20:05:55 +08:00
angrylid
a45dc6efda feat: add animation to ProfileNew component (#252)
* chore: add .vscode to .gitignore

* feat: add animation to ProfileNew component
2022-10-29 20:02:51 +08:00
GyDi
9e56b9fbb5 fix: icon button color 2022-10-29 19:22:15 +08:00
GyDi
5504994cb9 feat: check remote profile field 2022-10-28 13:00:52 +08:00
GyDi
acff6d0432 fix: init system proxy correctly 2022-10-28 01:33:21 +08:00
GyDi
eea9cb7c5b fix: open file 2022-10-28 01:26:45 +08:00
GyDi
c0ddddfb1f fix: reset proxy 2022-10-28 01:06:54 +08:00
GyDi
f7dab3ca56 fix: init config error 2022-10-28 01:02:47 +08:00
GyDi
e11b4038a3 feat: system tray support zh language 2022-10-28 00:40:29 +08:00
GyDi
b635e64803 fix: adjust reset proxy 2022-10-27 21:14:14 +08:00
GyDi
20a194b49a fix: adjust code 2022-10-27 21:13:27 +08:00
GyDi
33a5fb8837 fix: add https proxy 2022-10-26 17:11:54 +08:00
GyDi
59dae640db fix: auto scroll into view when sorted proxies changed 2022-10-26 01:24:06 +08:00
GyDi
a6ac75e97b feat: display delay check result timely 2022-10-26 01:11:02 +08:00
GyDi
cc5b33a8ec fix: refresh proxies interval, close #235 2022-10-26 01:08:34 +08:00
GyDi
6e1a627b84 fix: style 2022-10-25 23:54:21 +08:00
GyDi
62d4c65e1c chore: update deps 2022-10-25 23:54:10 +08:00
GyDi
600134a3ac fix: fetch profile with system proxy, close #249 2022-10-25 23:45:24 +08:00
LooSheng
df14af7337 fix: The profile is replaced when the request fails. (#246) 2022-10-23 17:22:26 +08:00
GyDi
2f740b570d fix: default dns config 2022-10-22 22:26:40 +08:00
GyDi
294d980b52 fix: kill clash when exit in service mode, close #241 2022-10-22 13:55:06 +08:00
GyDi
c9d9909d74 chore: update mmdb 2022-10-22 13:30:08 +08:00
GyDi
90eeabae7b feat: update profile with system proxy/clash proxy 2022-10-18 23:19:21 +08:00
GyDi
a32c77c5f1 chore: adjust code 2022-10-17 23:26:25 +08:00
GyDi
910846f2ce fix: icon button color inherit 2022-10-16 14:40:45 +08:00
GyDi
35d0438261 fix: app version to string 2022-10-16 14:38:57 +08:00
GyDi
515af472ce fix: break loop when core terminated 2022-10-14 14:46:15 +08:00
GyDi
f062f7f9fe feat: change global mode ui, close #226 2022-10-13 11:54:52 +08:00
GyDi
f68378041f feat: default user agent same with app version 2022-10-12 11:28:47 +08:00
GyDi
30ef3057ac v1.1.1 2022-10-11 23:03:12 +08:00
GyDi
48f3a934c9 chore: update log 2022-10-11 23:02:49 +08:00
GyDi
38d1fde84f fix: api error handle 2022-10-11 22:51:18 +08:00
GyDi
dd78670a4b fix: clash meta not load geoip, close #212 2022-10-11 22:24:54 +08:00
GyDi
b8a8190a43 fix: sort proxy during loading, close #221 2022-10-11 22:06:55 +08:00
GyDi
2d0989342f fix: not create windows when enable slient start 2022-10-11 21:50:00 +08:00
GyDi
15ff9b06a1 fix: root background color 2022-10-11 20:49:03 +08:00
GyDi
41b19f69de fix: create window correctly 2022-10-11 00:57:34 +08:00
GyDi
f2bd6f1fce fix: set_activation_policy 2022-10-10 22:15:21 +08:00
GyDi
ec94218a4b chore: rm aarch64 ci 2022-09-28 18:50:33 +08:00
GyDi
c2e5c7cf38 chore: test ci 2022-09-28 17:26:32 +08:00
GyDi
bac5734527 chore: test ci 2022-09-28 17:12:27 +08:00
GyDi
bef4033d94 chore: fix ci 2022-09-28 16:04:05 +08:00
GyDi
64216cba67 chore: fix test ci 2022-09-28 15:19:50 +08:00
Particle_G
a9d6167a9f chore: add support for windows arm64, close #216 (#209) 2022-09-28 15:13:24 +08:00
GyDi
6c6b40548f fix: disable spell check 2022-09-28 14:15:22 +08:00
苏业钦
a916d88e85 chore: add support linux arm64 (#215) 2022-09-28 10:40:41 +08:00
GyDi
ab2f0548a3 chore: clash meta linux use compatible version 2022-09-27 00:16:22 +08:00
GyDi
e30ba07285 feat: optimize config feedback 2022-09-26 20:46:29 +08:00
GyDi
1b336d973d fix: adjust init launch on dev 2022-09-26 01:35:19 +08:00
GyDi
7b1866737f v1.1.0 2022-09-26 01:27:56 +08:00
GyDi
eedc4ab648 chore: update log 2022-09-26 01:27:32 +08:00
GyDi
5e429c7a94 fix: ignore disable auto launch error 2022-09-26 01:09:05 +08:00
GyDi
57d23eb043 fix(macos): set auto launch path to application 2022-09-25 23:36:55 +08:00
GyDi
5ebd9be89a fix: i18n 2022-09-25 01:41:46 +08:00
GyDi
5a743779e2 feat: show connections with table layout 2022-09-25 01:35:21 +08:00
GyDi
0495062110 fix: style 2022-09-24 20:55:40 +08:00
GyDi
d522191f69 fix: save enable log on localstorage 2022-09-24 19:03:14 +08:00
Priestch
bbbdc8b7a6 fix: typo in api.ts (#207) 2022-09-24 18:48:11 +08:00
GyDi
c00ed8aa5a chore: use ubuntu latest 2022-09-24 16:08:39 +08:00
GyDi
0beaa94068 feat: show loading on proxy group delay check 2022-09-24 15:31:09 +08:00
GyDi
96e76665d6 fix: refresh clash ui await patch 2022-09-24 14:01:28 +08:00
Shun Li
6423a29600 feat: add chains[0] and process to connections display (#205)
* add chains[0] display

* add metadata.process to connections
2022-09-24 13:06:32 +08:00
GyDi
8bddf30dcf refactor(hotkey): use tauri global shortcut 2022-09-23 15:31:01 +08:00
GyDi
a6b2db182d feat: adjust connection page ui 2022-09-23 00:08:52 +08:00
GyDi
b9f3f9d859 Merge pull request #197 from FoundTheWOUT/main
Support ordering connection
2022-09-22 22:55:46 +08:00
GyDi
6331447dcd feat: yaml merge key 2022-09-21 22:15:24 +08:00
GyDi
4213ee660f feat: toggle log ws 2022-09-20 22:15:28 +08:00
GyDi
3bd9287c5d chore: update deps 2022-09-20 21:26:38 +08:00
GyDi
b23d3f7c8b feat: add rule page 2022-09-18 23:19:02 +08:00
GyDi
f8d9e5e027 feat: hotkey viewer 2022-09-18 15:52:53 +08:00
GyDi
8fa7fb3b1f feat: refresh ui when hotkey clicked 2022-09-18 15:50:03 +08:00
FoundTheWOUT
63d92a0872 Support ordering connection 2022-09-15 15:52:12 +08:00
GyDi
509d83365e feat: support hotkey (wip) 2022-09-14 01:19:02 +08:00
GyDi
d6ab73c905 fix: remove dead code 2022-09-12 13:42:21 +08:00
GyDi
5a93ba05d5 fix: style 2022-09-12 13:42:13 +08:00
GyDi
2d2fdf0b1e fix: handle is none 2022-09-12 00:45:19 +08:00
GyDi
cf96622261 fix: unused 2022-09-12 00:02:25 +08:00
GyDi
47c8ccb0e5 refactor: optimize 2022-09-11 20:58:55 +08:00
GyDi
02fdb8778b fix: style 2022-09-11 18:38:51 +08:00
GyDi
6bed7f0e66 fix: windows logo size 2022-09-11 17:50:58 +08:00
GyDi
7597d335b9 feat: hide window on macos 2022-09-09 16:42:35 +08:00
GyDi
f32c5ba244 fix: do not kill sidecar during updating 2022-09-09 16:33:04 +08:00
GyDi
ab53ab21e2 fix: delay update config 2022-09-09 16:30:27 +08:00
GyDi
acfe5dbb49 fix: reduce logo size 2022-09-09 16:19:13 +08:00
GyDi
f9b91fa189 feat: system proxy setting 2022-09-07 01:51:43 +08:00
GyDi
2462e68ba1 fix: window center 2022-09-06 22:18:06 +08:00
GyDi
019b2a1681 chore: update resource 2022-09-06 15:32:22 +08:00
GyDi
e94a07b677 fix: log level warn value 2022-09-06 14:42:58 +08:00
GyDi
c058c29755 feat: change default singleton port and support to change the port 2022-09-06 00:45:01 +08:00
GyDi
9e7c7ac163 feat: log info 2022-09-05 20:30:39 +08:00
GyDi
fcee41f00d feat: kill clash by pid 2022-09-05 16:30:29 +08:00
GyDi
ff2c1bf8ed feat: change clash port in dialog 2022-09-05 02:12:25 +08:00
GyDi
a2cf26e7ed feat: add proxy item check loading 2022-09-04 23:53:48 +08:00
GyDi
71e6900375 feat: compatible with proxy providers health check 2022-09-04 22:55:54 +08:00
GyDi
3bdc98bd12 v1.0.6 2022-09-03 00:40:50 +08:00
GyDi
68d2a6e951 chore: update log 2022-09-03 00:39:39 +08:00
GyDi
acf47ac947 chore: update deps 2022-09-02 23:06:00 +08:00
GyDi
fda24e5f5a chore: update deps 2022-09-02 01:35:06 +08:00
GyDi
05eca8e4d8 chore: use ubuntu 18.04 2022-09-02 01:27:06 +08:00
GyDi
b915f3b1a9 feat: add empty ui 2022-09-02 01:09:38 +08:00
GyDi
ab58968f4d feat: complete i18n 2022-09-02 01:05:45 +08:00
GyDi
820d1e7570 fix: increase delay checker concurrency 2022-09-02 00:41:43 +08:00
GyDi
a120c8cf98 fix: external controller allow lan 2022-09-02 00:24:19 +08:00
GyDi
5a35ea116f chore: update clash meta version 2022-09-02 00:11:52 +08:00
GyDi
e72ad1f030 fix: remove useless optimizations 2022-08-31 21:44:23 +08:00
GyDi
a17362437a chore: test ci 2022-08-29 17:26:09 +08:00
GyDi
8643dc43b1 chore: update clash 2022-08-29 11:11:55 +08:00
GyDi
e1c869a358 chore: add test ci os version 2022-08-29 11:02:14 +08:00
GyDi
2f5b8d9abe Merge branch 'main' of github.com:zzzgydi/clash-verge 2022-08-24 22:41:36 +08:00
GyDi
db324f54eb chore: update auto launch 2022-08-24 22:41:12 +08:00
GyDi
3242efb1a2 fix: reduce unsafe unwrap 2022-08-23 15:19:04 +08:00
FoundTheWOUT
30f9f1a021 fix: timer restore at app launch 2022-08-23 10:35:36 +08:00
GyDi
3f58d05aa7 fix: adjust log text 2022-08-19 17:11:59 +08:00
GyDi
c611a51575 fix: only script profile can display console 2022-08-17 01:45:37 +08:00
GyDi
73458dcd28 v1.0.5 2022-08-16 23:56:29 +08:00
GyDi
23ebeb1cc0 chore: update log 2022-08-16 23:56:04 +08:00
GyDi
a7ba9f1886 fix: fill button title attr 2022-08-16 23:46:33 +08:00
GyDi
d5192e2244 feat: windows portable version do not check update 2022-08-16 01:53:40 +08:00
GyDi
7eb595170f fix: do not reset system proxy when consistent 2022-08-16 01:27:32 +08:00
GyDi
bd576ca808 fix: adjust web ui item style 2022-08-16 01:08:54 +08:00
GyDi
2f8146b11f fix: clash field state error 2022-08-16 00:48:06 +08:00
GyDi
7f321c89cb feat: adjust clash info parsing logs 2022-08-15 20:21:43 +08:00
GyDi
fa65f606b8 fix: badge color error 2022-08-15 20:15:44 +08:00
GyDi
7a3285adaf fix: web ui port value error 2022-08-15 20:14:33 +08:00
GyDi
8bf78fef10 fix: delay show window 2022-08-15 01:37:26 +08:00
GyDi
78f97ce4df feat: adjust runtime config 2022-08-15 01:30:37 +08:00
GyDi
66ccbf70f8 feat: support restart app on tray 2022-08-15 01:22:39 +08:00
GyDi
cb48600b40 fix: adjust dialog action button variant 2022-08-15 00:55:35 +08:00
GyDi
33ce235713 feat: optimize profile page 2022-08-14 23:10:19 +08:00
GyDi
f1a68ece01 fix: script code error 2022-08-12 23:41:25 +08:00
GyDi
dd563360af fix: script exception handle 2022-08-12 11:14:34 +08:00
GyDi
7f6dac4271 feat: refactor 2022-08-12 03:20:55 +08:00
GyDi
178fd8e828 feat: adjust tun mode config 2022-08-11 03:26:08 +08:00
GyDi
1641e02a7d feat: reimplement enhanced mode 2022-08-11 02:55:10 +08:00
GyDi
cfd04e9bb4 feat: use rquickjs crate 2022-08-10 13:05:48 +08:00
GyDi
38effaf740 feat: reimplement enhanced mode 2022-08-09 21:15:55 +08:00
GyDi
a68eb4a73e chore: format 2022-08-09 14:33:47 +08:00
GyDi
c02990ef98 fix: change fields 2022-08-09 14:31:59 +08:00
FoundTheWOUT
5aa7d5ffe9 fix: silent start (#150) 2022-08-09 14:14:06 +08:00
GyDi
4942b0fca5 fix: save profile when update 2022-08-08 23:17:22 +08:00
GyDi
aed1bdff5a fix: list compare wrong 2022-08-08 23:16:28 +08:00
GyDi
929c840006 fix: button color 2022-08-08 23:15:05 +08:00
GyDi
57aef1d3c2 chore: rm code 2022-08-08 22:30:13 +08:00
GyDi
c1734a094c fix: limit theme mode value 2022-08-08 22:28:44 +08:00
GyDi
99c46685ac feat: finish clash field control 2022-08-08 22:14:03 +08:00
GyDi
066b08040a feat: clash field viewer wip 2022-08-08 01:51:30 +08:00
GyDi
35de2334fb fix: add valid clash field 2022-08-07 20:52:50 +08:00
GyDi
5564c966a5 feat: support web ui 2022-08-06 21:56:54 +08:00
GyDi
0891b5e7b7 feat: adjust setting page style 2022-08-06 03:48:03 +08:00
GyDi
f3341f201f refactor: ts path alias 2022-08-06 02:35:11 +08:00
GyDi
bf0dafabe2 fix: icon style 2022-08-06 01:44:33 +08:00
GyDi
60f6587169 fix: reduce unwrap 2022-07-30 23:32:52 +08:00
GyDi
a9bf32919e feat: runtime config viewer 2022-07-25 01:20:13 +08:00
GyDi
7633f9f88b chore: fix updater script 2022-07-18 12:55:59 +08:00
GyDi
9b56233938 v1.0.4 2022-07-17 17:56:07 +08:00
GyDi
65074264b8 chore: update log 2022-07-17 17:52:41 +08:00
GyDi
4f6fceb87f feat: improve log rule 2022-07-17 17:39:44 +08:00
GyDi
659fdd1d37 fix: import mod 2022-07-17 16:51:54 +08:00
GyDi
8bce2ce040 feat: theme mode support follows system 2022-07-17 16:02:17 +08:00
GyDi
55cc83a5d4 chore: update deps 2022-07-17 14:54:09 +08:00
GyDi
6a51e93ded refactor: mode manage on tray 2022-07-13 02:26:54 +08:00
GyDi
fcf570e96e chore: rm code 2022-07-13 01:07:29 +08:00
GyDi
cbc184e953 fix: add tray separator 2022-07-13 01:05:53 +08:00
GyDi
98f9063352 Merge pull request #128 from Limsanity/main
feat: support switch proxy mode
2022-07-13 01:02:14 +08:00
limsanity
c278f1af00 style: resolve formatting problem 2022-07-13 00:54:47 +08:00
limsanity
fbb17a0ba5 feat(system tray): support switch rule/global/direct/script mode in system tray 2022-07-13 00:43:27 +08:00
GyDi
8637a9823e chore: update deps 2022-07-11 00:41:21 +08:00
GyDi
d717fe7e8c chore: update clash version 2022-07-08 01:42:14 +08:00
GyDi
5e8dfe7267 chore: update aarch rule 2022-07-07 02:54:20 +08:00
GyDi
aaa4fbcdbd chore: aarch upload script 2022-07-07 02:53:53 +08:00
GyDi
150f0cf486 fix: instantiate core after init app, close #122 2022-07-06 15:14:33 +08:00
GyDi
711b220a05 chore: update clash version 2022-07-06 00:54:01 +08:00
GyDi
b615c485f7 chore: update deps 2022-07-06 00:49:09 +08:00
GyDi
2f3b6b29ae fix: rm macOS transition props 2022-07-05 01:24:23 +08:00
GyDi
7aecd83c4a chore: update deps 2022-07-05 00:52:22 +08:00
GyDi
3b460ab91f chore: change tray icon 2022-06-22 01:26:25 +08:00
GyDi
661c0eb970 v1.0.3 2022-06-20 02:09:12 +08:00
GyDi
f4a1f1fdc8 chore: update log 2022-06-20 02:06:46 +08:00
GyDi
b5432c3728 chore: update clash version 2022-06-20 01:47:15 +08:00
GyDi
6d0625c409 fix: improve external-controller parse and log 2022-06-20 01:36:56 +08:00
GyDi
2f1ea08b8a chore: update macos icon 2022-06-18 18:41:53 +08:00
GyDi
a092da1943 fix: show windows on click 2022-06-18 17:28:37 +08:00
GyDi
43ef3cc562 chore: new logo for mac 2022-06-18 17:28:05 +08:00
GyDi
047774475c chore: update alpha ci 2022-06-17 01:18:19 +08:00
GyDi
91b8504df5 chore: update tauri 2022-06-17 01:16:46 +08:00
GyDi
a4fb2dfcf8 fix: adjust update profile notice error 2022-06-15 02:42:04 +08:00
GyDi
ba16ec02e5 fix: style issue on mac 2022-06-15 02:41:37 +08:00
GyDi
55b7af2623 chore: update version 2022-06-15 01:15:05 +08:00
GyDi
b428eff10e feat: improve yaml file error log 2022-06-14 01:40:02 +08:00
GyDi
35aee15b6d chore: update deps 2022-06-14 01:23:58 +08:00
GyDi
6f53c1bfde Merge pull request #104 from FoundTheWOUT/main
fix: check script run on all OS
2022-06-12 22:25:05 +08:00
FoundTheWOUT
db0230ed75 fix: check script run on all OS 2022-06-05 23:55:41 +08:00
GyDi
7fa3c1e12a feat: save proxy page state 2022-06-04 18:55:39 +08:00
GyDi
3b5993652f chore: portable script 2022-06-02 00:56:05 +08:00
GyDi
be4ad8947f chore: updater script 2022-06-01 10:06:08 +08:00
GyDi
2ead15e78e v1.0.2 2022-06-01 01:26:05 +08:00
GyDi
aa0740ff94 chore: update log 2022-06-01 01:25:39 +08:00
GyDi
6d3f837820 fix: macOS disable transparent 2022-06-01 01:04:46 +08:00
GyDi
359b82c29c chore: rm some files 2022-06-01 00:49:52 +08:00
GyDi
b54171bc2c chore: rust cache 2022-06-01 00:47:04 +08:00
GyDi
936b2131e0 chore: adjust ci and script 2022-06-01 00:43:57 +08:00
GyDi
aaef6a9e9c chore: enable meta by default 2022-06-01 00:31:12 +08:00
GyDi
9327006e61 chore: rm file 2022-05-30 00:58:48 +08:00
GyDi
5225c841ae chore: adjust code 2022-05-30 00:57:31 +08:00
GyDi
8464e319fd chore: update deps 2022-05-29 23:35:22 +08:00
GyDi
98b8bd90ea fix: window transparent and can not get hwnd 2022-05-25 19:06:06 +08:00
GyDi
ae94993b09 fix: create main window 2022-05-25 16:45:18 +08:00
GyDi
72f10aaed1 chore: update deps 2022-05-25 16:12:48 +08:00
GyDi
4a74bae8c7 chore: update clash core 2022-05-25 16:08:58 +08:00
ctaoist
5164aec37b feat: light mode wip (#96)
* 关闭窗口释放UI资源

* windows 还有左键点击事件

* 兼容enhance profile

* bug 修复
2022-05-25 16:06:39 +08:00
GyDi
1581e9b1cd chore: update clash version 2022-05-18 14:14:37 +08:00
GyDi
536e3ffb11 chore: alpha ci 2022-05-18 14:07:41 +08:00
GyDi
d6103191ba chore: error text 2022-05-18 09:58:20 +08:00
GyDi
49e37a19a5 chore: ci 2022-05-17 12:35:42 +08:00
GyDi
d02431e260 chore: fix alpha ci 2022-05-17 12:34:30 +08:00
GyDi
f24f6ead92 chore: alpha ci 2022-05-17 11:41:45 +08:00
GyDi
b7bdb7ae50 chore: meta 2022-05-17 02:46:15 +08:00
GyDi
2dcf8ac96f chore: fix ci 2022-05-17 02:21:58 +08:00
GyDi
5421c94853 chore: test ci 2022-05-17 02:17:02 +08:00
GyDi
10f6bc092a chore: alpha 2022-05-17 02:11:29 +08:00
GyDi
be9ea4ea8e feat: clash meta core supports 2022-05-17 01:59:49 +08:00
GyDi
f5c6fa842a fix: adjust notice 2022-05-17 00:51:37 +08:00
GyDi
e0943ce905 fix: label text 2022-05-16 20:26:13 +08:00
GyDi
61e7df77a7 feat: script mode 2022-05-16 20:18:56 +08:00
GyDi
a5434360bc chore: test ci 2022-05-16 17:55:14 +08:00
GyDi
ba29c66e3b chore: update deps 2022-05-16 17:54:21 +08:00
GyDi
b3a72d55ae feat: clash meta core support (wip) 2022-05-16 01:52:50 +08:00
GyDi
c382ad1cc8 fix: icon path 2022-05-13 12:28:36 +08:00
GyDi
363e28f323 fix: icon issue 2022-05-13 02:46:06 +08:00
GyDi
d695656b8c fix: notice ui blocking 2022-05-13 02:29:43 +08:00
GyDi
31c6cbc0a2 fix: service mode error 2022-05-13 02:11:03 +08:00
GyDi
b93284bc2f chore: fix linux build ci 2022-05-11 12:39:41 +08:00
GyDi
99c855b01b chore: test ci 2022-05-11 12:20:20 +08:00
GyDi
c2eaedc959 chore: test ci 2022-05-11 12:12:55 +08:00
GyDi
a631cd67ec chore: test ci fix deps 2022-05-11 11:30:19 +08:00
GyDi
66fccd3c68 chore: test ci 2022-05-11 11:22:09 +08:00
GyDi
9a4ebf4daa chore: test ci 2022-05-11 10:51:28 +08:00
GyDi
91c14211c6 chore: continue on error 2022-05-11 01:26:11 +08:00
GyDi
1ea07c458b chore: test linux 2022-05-11 00:52:20 +08:00
GyDi
591add6e0c chore: test ci 2022-05-11 00:31:19 +08:00
GyDi
b99fff66df v1.0.1 2022-05-10 21:40:33 +08:00
GyDi
f54ba05b00 chore: update log 2022-05-10 21:40:07 +08:00
GyDi
6596fb00c7 fix: win11 drag lag 2022-05-09 14:41:26 +08:00
GyDi
df0b5a80dc chore: change default height 2022-05-09 14:01:46 +08:00
GyDi
8cdbb31dbe fix: rm unwrap 2022-05-09 14:01:14 +08:00
GyDi
0be4b1222d feat: reduce gpu usage when hidden 2022-05-07 14:43:52 +08:00
GyDi
18a6bfd73a feat: interval update from now field 2022-05-07 14:43:08 +08:00
GyDi
5e2271b237 feat: adjust theme 2022-05-06 14:04:39 +08:00
GyDi
798999d490 fix: edit profile info 2022-05-06 12:46:27 +08:00
GyDi
0e68c5e8bc feat: supports more remote headers close #81 2022-05-06 10:52:59 +08:00
GyDi
9694af82f4 feat: check the remote profile 2022-05-06 01:26:24 +08:00
GyDi
28a4386975 fix: change window default size 2022-05-06 01:15:15 +08:00
GyDi
a238f7beba chore: update deps 2022-05-06 01:14:13 +08:00
GyDi
75ba16281b chore: change default user agent 2022-05-06 00:42:20 +08:00
GyDi
ad65a278d4 chore: merge
feat: remove outdated config
2022-04-28 16:28:18 +08:00
tianyoulan
a2320b3f8d feat: fix typo 2022-04-28 15:35:17 +08:00
tianyoulan
ae12853ad0 feat: remove trailing comma 2022-04-28 15:05:10 +08:00
tianyoulan
68adf6dc2f feat: remove outdated config 2022-04-28 15:02:37 +08:00
GyDi
f88989bd4b fix: change service installer and uninstaller 2022-04-27 15:46:44 +08:00
GyDi
77ef3847ce v1.0.0 2022-04-26 00:50:48 +08:00
GyDi
b8291837fc chore: update log 2022-04-26 00:49:07 +08:00
GyDi
423a7f951a fix: adjust connection scroll 2022-04-26 00:37:28 +08:00
GyDi
557f5fe364 fix: adjust something 2022-04-25 20:00:11 +08:00
GyDi
5308970ad8 fix: adjust debounce wait time 2022-04-25 19:39:21 +08:00
GyDi
6b368953f4 feat: windows service mode ui 2022-04-25 16:12:04 +08:00
GyDi
2d0b63c29d feat: add some commands 2022-04-24 21:03:47 +08:00
GyDi
34e941c8cb feat: windows service mode 2022-04-24 21:00:17 +08:00
GyDi
76cf007fff chore: update check script 2022-04-24 15:35:30 +08:00
GyDi
321963be83 wip: windows service mode 2022-04-23 17:26:32 +08:00
GyDi
c733bda6c3 test: windows service 2022-04-21 21:28:44 +08:00
GyDi
e67b50b976 chore: adjust guard log 2022-04-21 19:51:20 +08:00
GyDi
9e3c080909 fix: adjust dns config 2022-04-21 19:50:22 +08:00
GyDi
cb661aaebd feat: add update interval 2022-04-21 14:26:41 +08:00
GyDi
573571978c chore: adjust 2022-04-21 14:24:35 +08:00
GyDi
dc492a2a0a chore: update clash version 2022-04-20 21:02:41 +08:00
GyDi
e47747dd0e feat: refactor and supports cron tasks 2022-04-20 20:39:27 +08:00
GyDi
4de944b41e feat: supports cron update profiles 2022-04-20 20:37:16 +08:00
GyDi
5f7a1fa5cd refactor: verge 2022-04-20 11:17:54 +08:00
GyDi
b8ad328cde refactor: wip 2022-04-20 01:44:47 +08:00
GyDi
3076fd19c1 refactor: mutex 2022-04-19 14:38:59 +08:00
GyDi
fac437b8c1 fix: traffic graph adapt to different fps 2022-04-19 13:55:26 +08:00
GyDi
697c25015e refactor: wip 2022-04-19 01:41:20 +08:00
GyDi
d83b404fc3 chore: check script proxy agent supports 2022-04-17 00:37:21 +08:00
GyDi
ab7313cbc4 feat: optimize traffic graph quadratic curve 2022-04-16 22:32:44 +08:00
GyDi
1b8d70322b feat: optimize the animation of the traffic graph 2022-04-16 17:28:30 +08:00
GyDi
844ffab4ed fix: optimize clash launch 2022-04-15 01:29:25 +08:00
GyDi
f5d0513d1f fix: reset after exit 2022-04-14 01:29:33 +08:00
GyDi
557abd4285 fix: adjust code 2022-04-14 01:29:02 +08:00
GyDi
f4f1a0fbc6 v0.0.29 2022-04-13 01:30:08 +08:00
GyDi
74e10dc012 chore: update log 2022-04-13 01:29:48 +08:00
GyDi
359812b7ed chore: update dep 2022-04-13 01:21:49 +08:00
GyDi
c2449e53c4 chore: rename green to portable 2022-04-13 01:17:40 +08:00
GyDi
1a91249da2 feat: system tray add tun mode 2022-04-13 01:09:51 +08:00
GyDi
b9162f9576 fix: adjust log 2022-04-13 00:49:30 +08:00
GyDi
f726e8a7b3 feat: supports change config dir 2022-04-12 23:09:32 +08:00
GyDi
2f284cfdc9 chore: update clash version 2022-04-12 23:04:19 +08:00
GyDi
b74696adba feat: add default user agent 2022-04-12 01:05:51 +08:00
GyDi
c8ccba0192 chore: rm console 2022-04-11 02:26:09 +08:00
GyDi
41b0e05f62 feat: connections page supports filter 2022-04-11 02:25:34 +08:00
GyDi
847d5f1b3b feat: log page supports filter 2022-04-11 01:46:33 +08:00
GyDi
0445f9dfc2 fix: check button hover style 2022-04-10 03:33:17 +08:00
GyDi
3001c780bd feat: optimize delay checker concurrency strategy 2022-04-10 02:58:48 +08:00
GyDi
68ad5e2320 feat: support sort proxy node and custom test url 2022-04-10 02:09:36 +08:00
GyDi
b5e229b19c chore: update deps 2022-04-10 01:31:05 +08:00
GyDi
453d798fcf refactor: proxy head 2022-04-08 01:35:18 +08:00
GyDi
451afdb660 v0.0.28 2022-04-07 01:28:30 +08:00
GyDi
d298bda92c chore: update log 2022-04-07 01:27:48 +08:00
GyDi
fd99ba6255 feat: handle remote clash config fields 2022-04-07 01:20:44 +08:00
GyDi
6ade0b2b1a fix: icon button color inherit 2022-04-06 22:52:00 +08:00
GyDi
9902003da9 fix: remove the lonely zero 2022-04-06 22:48:10 +08:00
GyDi
0ff2fcac11 chore: readme 2022-04-06 01:52:20 +08:00
GyDi
e80be8e7b6 v0.0.27 2022-04-06 01:21:33 +08:00
GyDi
fe0ad0f5cb chore: update log 2022-04-06 01:19:13 +08:00
GyDi
cb8e162f4e refactor: update profile menu 2022-04-06 01:13:06 +08:00
GyDi
ae8c30fe57 chore: add updater script 2022-04-05 23:24:52 +08:00
GyDi
ab82db9e22 chore: updater json supports arch field 2022-04-05 23:19:54 +08:00
GyDi
cb9d3098de chore: add ci 2022-04-05 20:21:53 +08:00
GyDi
c927419c99 refactor: enhanced mode ui component 2022-04-03 01:41:48 +08:00
GyDi
c009026961 feat: add text color 2022-04-02 17:18:38 +08:00
GyDi
51cf442fa5 fix: i18n add value 2022-04-02 13:49:48 +08:00
GyDi
954e3553ee fix: proxy page first render 2022-04-02 01:23:31 +08:00
GyDi
cde17385b4 feat: control final tun config 2022-04-01 23:55:44 +08:00
GyDi
5b9e078061 chore: add debug feature 2022-04-01 23:43:35 +08:00
GyDi
0290d9ddfc feat: support css injection 2022-04-01 02:08:42 +08:00
GyDi
eab671d102 fix: console warning 2022-04-01 01:16:23 +08:00
GyDi
f9a96ff914 feat: support theme setting 2022-03-31 23:38:00 +08:00
GyDi
3ec2b46d28 feat: add text color 2022-03-31 23:34:36 +08:00
GyDi
aec30b89e0 feat: add theme setting 2022-03-31 23:11:50 +08:00
GyDi
309c33e190 refactor: ui theme 2022-03-30 12:36:39 +08:00
GyDi
9c0276f97b chore: add api 2022-03-30 01:30:22 +08:00
GyDi
0a3402ff43 fix: icon button title 2022-03-30 01:29:25 +08:00
GyDi
bd82308024 chore: update tauri 2022-03-29 23:05:32 +08:00
GyDi
db3b634e62 fix: macOS transition flickers close #47 2022-03-29 01:39:54 +08:00
GyDi
40bcb22977 chore: update deps 2022-03-29 01:33:14 +08:00
GyDi
4bd94092f1 fix: csp image data 2022-03-28 23:17:11 +08:00
GyDi
cac1ce6895 chore: readme 2022-03-28 01:33:58 +08:00
GyDi
5c3696123a v0.0.26 2022-03-28 01:30:54 +08:00
GyDi
4140fc86ee chore: update log 2022-03-28 01:18:18 +08:00
GyDi
2ad771e7fd fix: close dialog after save 2022-03-28 01:09:34 +08:00
GyDi
5a38468144 refactor: optimize enhance mode strategy 2022-03-28 01:07:14 +08:00
GyDi
0a9c81772f fix: change to deep copy 2022-03-28 00:56:48 +08:00
GyDi
c9649ac501 feat: enhanced mode supports more fields 2022-03-27 20:36:13 +08:00
GyDi
6ea567742b chore: profile release 2022-03-27 01:38:40 +08:00
GyDi
f31349eaa0 feat: supports edit profile file 2022-03-27 00:58:17 +08:00
GyDi
9d44668d5f feat: supports silent start 2022-03-26 18:56:16 +08:00
GyDi
a12f58c1c7 feat: use crate open 2022-03-23 23:00:14 +08:00
GyDi
b5283eaaed fix: window style close #45 2022-03-23 21:43:13 +08:00
GyDi
4b6189af5f feat: enhance connections display order 2022-03-23 14:02:08 +08:00
GyDi
5d0ffbe453 chore: adjust style 2022-03-23 13:58:57 +08:00
GyDi
502706931e fix: manage global proxy correctly 2022-03-23 12:49:34 +08:00
GyDi
57c411288f fix: tauri csp 2022-03-23 01:47:35 +08:00
GyDi
b09b7b11a1 feat: save global selected 2022-03-22 12:38:59 +08:00
GyDi
1a7b3c7294 v0.0.25 2022-03-22 01:51:06 +08:00
GyDi
4678fc7dde chore: update log 2022-03-22 01:50:24 +08:00
GyDi
9f492fad49 chore: update clash core 2022-03-22 01:50:15 +08:00
GyDi
bd0a959e18 fix: windows style 2022-03-22 01:36:06 +08:00
GyDi
dd605e2610 fix: update state 2022-03-22 01:27:22 +08:00
inRm3D
9910f6b817 chore: readme (#43) 2022-03-21 21:55:45 +08:00
GyDi
991897aff4 chore: tauri feature 2022-03-21 11:40:52 +08:00
GyDi
3cde019208 fix: profile item loading state 2022-03-21 11:40:27 +08:00
GyDi
2f6efbed63 chore: tauri allowList 2022-03-20 13:16:50 +08:00
GyDi
5cff4e299b chore: update clash version 2022-03-20 12:37:55 +08:00
GyDi
bce33639da v0.0.24 2022-03-20 01:50:39 +08:00
GyDi
366c465cad chore: update log 2022-03-20 01:50:15 +08:00
GyDi
acc6e05bdc feat: system tray supports system proxy setting 2022-03-20 01:36:43 +08:00
GyDi
4ce15577cd feat: prevent context menu on Windows close #22 2022-03-19 19:28:53 +08:00
GyDi
98fa4d5e65 feat: create local profile with selected file 2022-03-19 19:21:55 +08:00
GyDi
7fe94076c7 feat: reduce the impact of the enhanced mode 2022-03-19 16:02:47 +08:00
GyDi
b8b0c8fa63 fix: adjust windows style 2022-03-19 15:35:59 +08:00
GyDi
e585e87bec feat: parse update log 2022-03-19 15:31:58 +08:00
GyDi
6a4924bb16 chore: update log supports 2022-03-19 14:04:58 +08:00
GyDi
ab0d516d91 chore: updater proxy 2022-03-19 11:14:12 +08:00
GyDi
b756ae39d0 chore: ci support linux 2022-03-19 10:50:44 +08:00
GyDi
c15c38ea8f chore: readme 2022-03-18 19:02:53 +08:00
GyDi
743963318f feat: fill i18n 2022-03-18 14:59:23 +08:00
GyDi
ed3fc50858 feat: dayjs i18n 2022-03-18 14:45:24 +08:00
GyDi
5b886fe6be feat: connections page simply support 2022-03-18 14:43:22 +08:00
GyDi
7074bbc405 feat: add wintun.dll by default 2022-03-18 11:49:00 +08:00
GyDi
ef314c1707 fix: change mixed port error 2022-03-18 11:39:02 +08:00
GyDi
a3e7626dd9 chore: add dev feature 2022-03-18 10:59:35 +08:00
GyDi
5cb5d74eed chore: rename temp dir 2022-03-18 10:20:24 +08:00
GyDi
6eee10d46d chore: resolve wintun.dll 2022-03-17 19:29:30 +08:00
GyDi
2d95f2b0d6 fix: auto launch path 2022-03-16 18:44:25 +08:00
GyDi
ec41bb9c70 fix: tun mode config 2022-03-15 14:23:57 +08:00
GyDi
2d1780b1cf fix: adjsut open cmd error 2022-03-15 12:51:39 +08:00
GyDi
d9ce99887c chore: fix ci env 2022-03-15 00:00:20 +08:00
GyDi
9b9cc90414 v0.0.23 2022-03-14 01:34:26 +08:00
GyDi
c1eb539a5c feat: event emit when clash config update 2022-03-13 01:37:41 +08:00
GyDi
e1793f57ef chore: fix ci 2022-03-13 00:59:42 +08:00
GyDi
dbd09a8743 chore: update tauri action 2022-03-13 00:58:49 +08:00
GyDi
0d189ca617 chore: green version bundle 2022-03-13 00:52:31 +08:00
GyDi
dc9bcc40ee chore: rm then 2022-03-12 23:58:20 +08:00
GyDi
4991f7ff39 feat: i18n supports 2022-03-12 23:07:45 +08:00
GyDi
a393b8b122 fix: parse external-controller 2022-03-12 21:35:20 +08:00
GyDi
c73b354386 fix: config file case close #18 2022-03-12 21:03:17 +08:00
GyDi
392ecee3ff chore: merge 2022-03-12 18:45:07 +08:00
ttyS3
bae721c49e chore: show open app and log dir failed message 2022-03-12 18:04:56 +08:00
GyDi
4e806e21a6 chore: adjust error log 2022-03-12 15:57:16 +08:00
GyDi
ec0fdf83b2 feat: change open command on linux
refactor(logging): refine open dir failed log message, add Linux support
2022-03-12 15:40:23 +08:00
ttyS3
cb94d8414f feat: add Linux open dir support
refactor(logging): refine open dir failed log message
2022-03-12 03:18:25 +08:00
GyDi
8890051c17 fix: patch item option 2022-03-11 19:56:56 +08:00
GyDi
cf00c9476f fix: user agent not works 2022-03-11 19:50:51 +08:00
GyDi
b2a24c7abd v0.0.22 2022-03-10 02:27:00 +08:00
GyDi
732a1f4694 chore: rm dead code 2022-03-10 02:25:35 +08:00
GyDi
4c5aa7084e fix: external-controller 2022-03-10 02:19:06 +08:00
GyDi
fe1fea671c feat: support more options for remote profile 2022-03-10 02:03:55 +08:00
GyDi
04c754c0ac chore: reduce icon size on macOS 2022-03-10 01:04:53 +08:00
GyDi
754c22c84e chore: adjust icon 2022-03-10 00:04:59 +08:00
GyDi
629331870b chore: add linux link 2022-03-09 20:14:15 +08:00
GyDi
78774315cb feat: linux system proxy 2022-03-09 19:48:32 +08:00
GyDi
36b9c07928 chore: update deps 2022-03-09 02:36:40 +08:00
GyDi
40a818630d fix: change proxy bypass on mac 2022-03-09 02:36:03 +08:00
GyDi
568511a4cf fix: kill sidecars after install still in test 2022-03-09 02:09:51 +08:00
GyDi
109fb39e09 fix: log some error 2022-03-09 02:00:56 +08:00
GyDi
68450d2042 chore: rename productName 2022-03-09 01:53:34 +08:00
GyDi
8a052bbed6 chore: update tauri version 2022-03-09 01:50:37 +08:00
GyDi
3afbb56640 fix: apply_blur parameter 2022-03-08 10:44:41 +08:00
GyDi
c0ad84a491 fix: limit enhanced profile range 2022-03-08 01:45:46 +08:00
GyDi
c72f17605c chore: update deps 2022-03-08 01:18:20 +08:00
GyDi
42fbee0cdb chore: add default bypass 2022-03-08 00:39:09 +08:00
GyDi
e9b7ec735f fix: profile updated field 2022-03-07 01:41:42 +08:00
GyDi
743788135f fix: profile field check 2022-03-07 01:30:32 +08:00
GyDi
8ea3e6fa26 fix: create dir panic 2022-03-07 01:06:21 +08:00
GyDi
f23c83e681 v0.0.21 2022-03-06 18:36:02 +08:00
GyDi
b615bda17e chore: default release body 2022-03-06 18:34:38 +08:00
GyDi
f7c7cd1d3c chore: update deps 2022-03-06 18:04:44 +08:00
GyDi
c7e7be4379 fix: only error when selected 2022-03-06 17:06:45 +08:00
GyDi
d63d49f246 feat: enhance profile status 2022-03-06 17:02:29 +08:00
GyDi
dad94edb20 feat: menu item refresh enhanced mode 2022-03-06 15:46:16 +08:00
GyDi
7108d5f3ab fix: enhanced profile consistency 2022-03-06 15:40:16 +08:00
GyDi
ef47a74920 feat: profile enhanced mode 2022-03-06 14:59:25 +08:00
GyDi
a43dab8057 feat: profile enhanced ui 2022-03-05 22:54:39 +08:00
GyDi
f44039b628 feat: profile item adjust 2022-03-05 19:04:20 +08:00
GyDi
08fa5205b0 fix: simply compatible with proxy providers 2022-03-05 16:18:44 +08:00
GyDi
b91daebd92 fix: component warning 2022-03-05 15:48:39 +08:00
GyDi
9cd6c5c624 chore: update copyright 2022-03-05 01:51:29 +08:00
GyDi
650e017b72 fix: when updater failed 2022-03-04 02:10:27 +08:00
GyDi
18f9d6dec5 fix: log file 2022-03-04 02:08:26 +08:00
GyDi
4df6571ad9 chore: update deps 2022-03-03 20:37:06 +08:00
GyDi
0f5923a10a chore: enhance wip 2022-03-03 01:58:05 +08:00
GyDi
bcdae1169e fix: result 2022-03-03 01:56:47 +08:00
GyDi
f260d5df49 feat: enhanced profile (wip) 2022-03-03 01:52:02 +08:00
GyDi
808b861dd1 v0.0.20 2022-03-02 01:59:53 +08:00
GyDi
17f1c487a8 feat: edit profile item 2022-03-02 01:58:16 +08:00
GyDi
8dc2c1a38f fix: cover profile extra 2022-03-02 01:45:00 +08:00
GyDi
220a494692 chore: adjust log field 2022-03-02 01:13:31 +08:00
GyDi
db6bc10196 chore: rm file 2022-03-02 00:58:07 +08:00
GyDi
1880363aeb feat: use nanoid 2022-03-02 00:56:05 +08:00
GyDi
19c7b59883 feat: compatible profile config 2022-03-01 11:05:33 +08:00
GyDi
749df89229 refactor: profile config 2022-03-01 08:58:47 +08:00
GyDi
444f2172fa v0.0.19 2022-02-28 01:59:53 +08:00
GyDi
77a77c0ea7 wip: refactor profile 2022-02-28 01:59:13 +08:00
GyDi
dbf380a0d1 refactor: use anyhow to handle error 2022-02-28 01:34:25 +08:00
GyDi
ade34f5217 fix: display menu only on macos 2022-02-27 01:47:56 +08:00
GyDi
e89607799a fix: proxy global showType 2022-02-27 01:33:22 +08:00
GyDi
d05d8d6a9e feat: native menu supports 2022-02-27 01:29:57 +08:00
GyDi
b6aa50d3dc chore: update deps 2022-02-27 01:16:43 +08:00
GyDi
9df361935f feat: filter proxy and display type 2022-02-27 00:58:14 +08:00
GyDi
98b8a122b6 feat: use lock fn 2022-02-26 17:39:36 +08:00
GyDi
c7232522ee feat: refactor proxy page 2022-02-26 17:39:08 +08:00
GyDi
5280f1d745 feat: proxy group auto scroll to current 2022-02-26 01:21:39 +08:00
GyDi
b52a081e7b v0.0.18 2022-02-25 02:13:41 +08:00
GyDi
f981a44861 chore: ci 2022-02-25 02:12:55 +08:00
GyDi
d7c5ce0750 feat: clash tun mode supports 2022-02-25 02:09:39 +08:00
GyDi
9ccc66ca1e feat: use enhanced guard-state 2022-02-25 01:22:33 +08:00
GyDi
8606af3616 feat: guard state supports debounce guard 2022-02-25 01:21:13 +08:00
GyDi
f6e821ba6b feat: adjust clash version display 2022-02-24 23:04:18 +08:00
GyDi
e8dbcf819b feat: hide command window 2022-02-24 22:46:12 +08:00
GyDi
bbe2ef4e8e fix: use full clash config 2022-02-23 23:23:07 +08:00
GyDi
dd15455031 feat: enhance log data 2022-02-23 02:00:45 +08:00
GyDi
12ac7bb338 chore: update readme 2022-02-22 21:54:33 +08:00
GyDi
46ef348f0d v0.0.17 2022-02-22 02:08:31 +08:00
GyDi
1a55cca8af fix: reconnect websocket when restart clash 2022-02-22 02:05:22 +08:00
GyDi
81ee989f1f chore: enhance publish ci 2022-02-22 01:56:06 +08:00
GyDi
c9c06f8a3d fix: wrong exe path 2022-02-21 22:33:37 +08:00
GyDi
72127979c3 chore: use tauri cli 2022-02-21 22:31:00 +08:00
GyDi
1ad3ddef94 v0.0.16 2022-02-21 01:33:11 +08:00
GyDi
97ec5eabf7 feat: change window style 2022-02-20 23:46:13 +08:00
GyDi
e12e3a3f2d feat: fill verge template 2022-02-20 20:12:55 +08:00
GyDi
4ff625f23b feat: enable customize guard duration 2022-02-20 20:11:39 +08:00
GyDi
e38dcd85ac feat: system proxy guard 2022-02-20 18:53:21 +08:00
GyDi
0245baf1b6 feat: enable show or hide traffic graph 2022-02-19 18:14:40 +08:00
GyDi
10b55c043c fix: patch verge config 2022-02-19 17:32:23 +08:00
GyDi
457655b416 feat: traffic line graph 2022-02-19 17:02:24 +08:00
GyDi
7e4506c860 chore: update deps 2022-02-19 00:34:09 +08:00
GyDi
794d376348 feat: adjust profile item ui 2022-02-19 00:23:47 +08:00
GyDi
c60578f5b5 feat: adjust fetch profile url 2022-02-19 00:09:36 +08:00
GyDi
3a9a392a77 feat: inline config file template 2022-02-18 23:57:13 +08:00
GyDi
a13d4698be chore: update check script 2022-02-18 23:49:39 +08:00
GyDi
c046a1993e fix: fetch profile panic 2022-02-17 13:44:26 +08:00
GyDi
f709117cc4 feat: kill sidecars when update app 2022-02-17 02:10:25 +08:00
GyDi
30dd298fca feat: delete file 2022-02-17 01:58:12 +08:00
GyDi
0ff8bb8090 feat: lock some async functions 2022-02-17 01:42:25 +08:00
GyDi
74bbd3c3a2 chore: tauri updater env 2022-02-16 11:02:00 +08:00
GyDi
2e82eaf59a chore: add pubkey 2022-02-16 10:59:31 +08:00
GyDi
7f1df1f1bd v0.0.15 2022-02-16 03:22:25 +08:00
GyDi
3c79238a44 feat: support open dir 2022-02-16 03:21:34 +08:00
GyDi
0aa2565df3 fix: spawn command 2022-02-16 02:43:52 +08:00
GyDi
22b11db16e feat: change allow list 2022-02-16 02:42:56 +08:00
GyDi
d0e678b5e9 feat: support check delay 2022-02-16 02:22:01 +08:00
GyDi
e7bba968b3 fix: import error 2022-02-15 01:36:19 +08:00
GyDi
4934a24293 feat: scroll to proxy item 2022-02-15 01:34:10 +08:00
GyDi
ccb68bcda9 chore: update deps 2022-02-15 00:33:58 +08:00
GyDi
66bf4ba3ad fix: not open file when new profile 2022-02-15 00:21:34 +08:00
GyDi
78a0cfd052 feat: edit system proxy bypass 2022-02-14 01:26:24 +08:00
GyDi
8548373742 feat: disable user select 2022-02-13 19:52:35 +08:00
GyDi
2dfd725ee0 fix: reset value correctly 2022-02-13 19:40:31 +08:00
GyDi
5b779b4f14 feat: new profile able to edit name and desc 2022-02-13 19:33:24 +08:00
GyDi
fc48aa7155 chore: adjust files 2022-02-13 19:27:24 +08:00
GyDi
6193a842f4 feat: update tauri version 2022-02-13 18:45:03 +08:00
GyDi
eb86b471fe fix: something 2022-02-12 21:12:39 +08:00
GyDi
2b52584547 fix: menu without fragment 2022-02-10 01:42:52 +08:00
GyDi
6e3cc57f48 feat: display clash core version 2022-02-10 01:35:55 +08:00
GyDi
f2c04621a5 feat: adjust profile item menu 2022-02-10 01:14:57 +08:00
GyDi
3ed6938d4a v0.0.14 2022-02-09 02:11:01 +08:00
GyDi
99fec25ed5 feat: profile item ui 2022-02-09 02:08:27 +08:00
GyDi
73758ad1fd fix: proxy list error 2022-02-09 02:02:29 +08:00
GyDi
c53fe0ed1f feat: support new profile 2022-02-07 17:26:05 +08:00
GyDi
6082c2bcac feat: support open command for viewing 2022-02-07 16:45:20 +08:00
GyDi
069abed784 chore: add item template yaml 2022-02-02 20:56:06 +08:00
GyDi
7d8fa4d78a fix: something 2022-01-31 23:34:58 +08:00
GyDi
76081f8d89 fix: macos auto launch fail 2022-01-31 23:32:41 +08:00
GyDi
5ef4285558 chore: update deps and app name 2022-01-31 23:27:11 +08:00
GyDi
aa7df4282e chore: cargo update 2022-01-28 22:02:17 +08:00
GyDi
0ef1d5d0de chore: update clash version 2022-01-28 22:00:15 +08:00
GyDi
cceb4bb81f v0.0.13 2022-01-25 02:11:00 +08:00
GyDi
f0f45e007d feat: global proxies use virtual list 2022-01-25 02:08:10 +08:00
GyDi
6a8ffe1642 feat: enable change proxy mode 2022-01-25 01:51:44 +08:00
GyDi
3a73868c10 feat: update styles 2022-01-25 01:48:26 +08:00
GyDi
ab1b5897a6 feat: manage clash mode 2022-01-24 23:13:13 +08:00
GyDi
f94734a5c8 chore: update readme 2022-01-22 23:57:39 +08:00
GyDi
61b86c9584 v0.0.12 2022-01-21 03:09:21 +08:00
GyDi
1ac1d6e903 chore: update ci 2022-01-21 03:08:40 +08:00
GyDi
b6c58f74c0 fix: type error 2022-01-21 03:08:20 +08:00
GyDi
c88e99d87c v0.0.12 2022-01-21 02:59:33 +08:00
GyDi
8d7ab9d05e refactor: rename profiles & command state 2022-01-21 02:57:15 +08:00
GyDi
47155a4a29 feat: change system porxy when changed port 2022-01-21 02:50:13 +08:00
GyDi
d0b87fd7c3 feat: enable change mixed port 2022-01-21 02:32:23 +08:00
GyDi
d49fd37656 feat: manage clash config 2022-01-21 02:31:44 +08:00
GyDi
0bd29d71be chore: add ahooks 2022-01-21 02:29:45 +08:00
GyDi
4b5b62c8ae fix: restart clash should update something 2022-01-21 00:29:33 +08:00
GyDi
65fb2ca2d5 feat: enable update clash info 2022-01-20 23:41:08 +08:00
GyDi
0d5bfc0997 feat: rename edit as view 2022-01-19 23:58:34 +08:00
GyDi
4f02c373c2 chore: adjust ci 2022-01-19 23:55:05 +08:00
GyDi
209a5b1207 fix: script error... 2022-01-19 00:49:23 +08:00
GyDi
95349eacab fix: tag error 2022-01-19 00:46:43 +08:00
GyDi
82ba604b99 fix: script error 2022-01-19 00:42:37 +08:00
GyDi
46a8dec655 feat: test auto gen update.json ci 2022-01-19 00:37:59 +08:00
GyDi
b5af234524 v0.0.11 2022-01-17 02:53:07 +08:00
GyDi
b5c41750f7 fix: remove cargo test 2022-01-17 02:50:19 +08:00
GyDi
6083824eec v0.0.11 2022-01-17 02:44:08 +08:00
GyDi
40977785c3 feat: adjust setting typography 2022-01-17 02:42:52 +08:00
GyDi
5eddf4f1aa feat: enable force select profile 2022-01-17 02:28:23 +08:00
GyDi
99a8e25411 feat: support edit profile item 2022-01-17 02:16:17 +08:00
GyDi
08587d8f2f fix: reduce proxy item height 2022-01-17 02:15:54 +08:00
GyDi
3480d50f61 feat: adjust control ui 2022-01-17 02:15:06 +08:00
GyDi
43af55252d feat: update profile supports noproxy 2022-01-16 22:57:42 +08:00
GyDi
9c43b31fc0 refactor: something 2022-01-16 18:30:25 +08:00
GyDi
9ec7184aa1 fix: put profile request with no proxy 2022-01-16 18:20:01 +08:00
GyDi
4e2cb30db7 refactor: notice caller 2022-01-16 17:56:43 +08:00
GyDi
9ca83d3291 chore: change ci 2022-01-16 16:03:53 +08:00
GyDi
6b2172d873 fix: ci strategy 2022-01-16 14:30:49 +08:00
GyDi
1a5d9f7dad chore: enhance ci 2022-01-16 14:27:41 +08:00
GyDi
a8425862f0 feat: rename page 2022-01-16 03:25:50 +08:00
GyDi
a3a3db6abb refactor: setting page 2022-01-16 03:22:37 +08:00
GyDi
d6c3bc57c0 feat: refactor and adjust ui 2022-01-16 03:11:07 +08:00
GyDi
59c09f90f9 feat: rm some commands 2022-01-16 03:09:36 +08:00
GyDi
d982b83e14 feat: change type 2022-01-15 21:58:13 +08:00
GyDi
cc0e930d34 feat: supports auto launch on macos and windows 2022-01-15 21:55:05 +08:00
GyDi
3c3d77fbea chore: add auto-launch 2022-01-15 21:00:19 +08:00
GyDi
da7453fdbf v0.0.10 2022-01-13 02:53:29 +08:00
GyDi
5fcd25506e feat: adjust proxy page 2022-01-13 02:51:30 +08:00
GyDi
4979a472de feat: press esc hide the window 2022-01-13 02:11:50 +08:00
GyDi
4e8d4f4591 fix: version update error 2022-01-12 22:19:44 +08:00
GyDi
0f5d2b15e0 v0.0.9 2022-01-12 02:55:07 +08:00
GyDi
7fc9631434 feat: show system proxy info 2022-01-12 02:54:50 +08:00
GyDi
df5953dd7b feat: support blur window 2022-01-12 02:27:29 +08:00
GyDi
8f5b2b4a0e chore: add macos startup todo 2022-01-11 23:14:43 +08:00
GyDi
43c63ffa70 chore: cargo update 2022-01-11 23:13:02 +08:00
GyDi
6779bc7459 v0.0.8 2022-01-11 02:27:04 +08:00
GyDi
664be2d0ba chore: update ci 2022-01-11 02:26:50 +08:00
GyDi
6113898b69 feat: windows support startup 2022-01-11 02:24:43 +08:00
GyDi
79aad6b5c2 feat: window self startup 2022-01-11 02:21:51 +08:00
GyDi
6da7757d36 fix: text 2022-01-10 22:57:21 +08:00
GyDi
dbb3cb8cc8 chore: ignore update json 2022-01-10 22:08:57 +08:00
GyDi
579f36a1dd fix: update profile after restart clash 2022-01-10 22:08:18 +08:00
GyDi
72c2b306cf fix: get proxies multiple times 2022-01-10 21:25:41 +08:00
GyDi
c2673cd396 docs: fix img width 2022-01-10 02:52:12 +08:00
GyDi
8eb152816a docs: update 2022-01-10 02:49:42 +08:00
GyDi
a0bc8a21a5 v0.0.7 2022-01-10 02:17:35 +08:00
GyDi
08e4d72758 feat: use tauri updater 2022-01-10 02:15:38 +08:00
GyDi
66340a27fa feat: support update checker 2022-01-10 02:05:35 +08:00
GyDi
83fe9835b6 v0.0.6 2022-01-09 21:36:43 +08:00
GyDi
4c1a50a3ca chore: ci add macos 2022-01-09 21:34:14 +08:00
GyDi
fe44a7b3bc feat: support macos proxy config 2022-01-09 21:19:35 +08:00
GyDi
e86d192db7 feat: custom window decorations 2022-01-08 22:23:48 +08:00
GyDi
ea8f1c52f9 feat: profiles add menu and delete button 2022-01-08 16:52:18 +08:00
GyDi
a4c1573c45 fix: delete profile item command 2022-01-08 14:21:12 +08:00
GyDi
182bf49ad0 chore: temp 2022-01-08 02:54:37 +08:00
GyDi
13e1ddbccd v0.0.5 2022-01-08 02:12:56 +08:00
GyDi
327b9a1757 chore: enhance ci 2022-01-08 02:12:12 +08:00
GyDi
18c48db7f7 chore: rename script 2022-01-08 01:56:28 +08:00
GyDi
b6543bd87f chore: update publish script 2022-01-08 01:51:24 +08:00
GyDi
9ad8f71d7c refactor: rename 2022-01-08 01:27:25 +08:00
GyDi
e369311fc2 refactor: impl structs methods 2022-01-07 23:29:20 +08:00
GyDi
72ff261fe3 chore: update clash version 2022-01-06 23:40:57 +08:00
GyDi
774c6f7e05 fix: initialize profiles state 2022-01-05 23:30:18 +08:00
GyDi
771af6ae08 chore: fixed tauri rev 2022-01-05 23:27:26 +08:00
GyDi
b3cd207444 feat: delay put profiles and retry 2022-01-05 02:01:32 +08:00
GyDi
03f9fa4bc2 refactor: impl as struct methods 2022-01-05 02:00:59 +08:00
GyDi
e32bfd9aab feat: Window Send and Sync 2022-01-04 21:29:04 +08:00
GyDi
7e47f8f893 chore: update tauri 2022-01-04 21:25:00 +08:00
GyDi
cb816e9653 feat: support restart sidecar tray event 2021-12-31 18:24:50 +08:00
GyDi
6b3e7cbc08 feat: prevent click same 2021-12-31 18:21:35 +08:00
GyDi
db4993ae9b fix: item header bgcolor 2021-12-31 18:19:53 +08:00
GyDi
4dc3cf6c6b feat: scroller stable 2021-12-31 18:19:17 +08:00
GyDi
2b84bbf3a8 feat: compatible with macos(wip) 2021-12-29 18:49:38 +08:00
GyDi
26ef4c9961 chore: change tauri to git repo 2021-12-29 18:47:29 +08:00
GyDi
4f56c38599 fix: null type error 2021-12-29 01:01:22 +08:00
GyDi
240f4dcfb1 chore: dev support macos 2021-12-29 01:01:09 +08:00
GyDi
ac6abd81c9 v0.0.4 2021-12-28 01:48:25 +08:00
GyDi
14bda4f3a5 feat: record selected proxy 2021-12-28 01:47:43 +08:00
GyDi
61b9670b45 feat: display version 2021-12-28 01:35:58 +08:00
GyDi
01e8db317e chore: save lock file 2021-12-28 00:14:15 +08:00
GyDi
92fc09493e chore: post version script 2021-12-27 23:07:56 +08:00
GyDi
d927209db7 v0.0.3 2021-12-27 23:06:46 +08:00
GyDi
e94007b21f v0.0.3 2021-12-27 23:06:45 +08:00
GyDi
18750f275a chore: update version 2021-12-27 10:21:49 +08:00
GyDi
d686a853f4 chore: update version 2021-12-27 10:21:10 +08:00
234 changed files with 11379 additions and 30203 deletions

View File

@@ -12,16 +12,14 @@ body:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue否则请在已有的issue下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题以便他人快速检索
4. 请 **务必** 查看 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本更新日志
5. 请 **务必** 尝试 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本,确定问题是否仍然存在
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本否则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/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 check out [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version update log
5. Please be sure to try the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version to ensure that the problem still exists
6. Please describe the problem in detail according to the template specification and try to update the Alpha version, otherwise the issue will be closed
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
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
- type: textarea
id: description
@@ -57,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: textarea
- type: checkboxes
id: os-labels
attributes:
label: 日志(勿上传日志文件,请粘贴日志内容) / Log (Do not upload the log file, paste the log content directly)
description: 提供完整或相关部分的Debug日志请在“软件左侧菜单”->“设置”->“日志等级”调整到debugVerge错误请把“杂项设置”->“app日志等级”调整到debug并重启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 debug, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
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日志请在“软件左侧菜单”->“设置”->“日志等级”调整到debugVerge错误请把“杂项设置”->“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

View File

@@ -1,58 +0,0 @@
name: I18N / 多语言相关
title: "[I18N] "
description: 用于多语言翻译、国际化相关问题或建议 / For issues or suggestions related to translations and internationalization
labels: ["I18n"]
type: "Task"
body:
- type: markdown
attributes:
value: |
## I18N 相关问题/建议
请用此模板提交翻译错误、缺失、建议或新增语言请求。
Please use this template for translation errors, missing translations, suggestions, or new language requests.
- type: textarea
id: description
attributes:
label: 问题描述 / Description
description: 详细描述你的 I18N 问题或建议 / Please describe your I18N issue or suggestion in detail
validations:
required: true
- type: input
id: language
attributes:
label: 相关语言 / Language
description: 例如 zh, en, jp, ru, ... / e.g. zh, en, jp, ru, ...
validations:
required: true
- type: textarea
id: suggestion
attributes:
label: 建议或修正内容 / Suggestion or Correction
description: 如果是翻译修正或建议,请填写建议的内容 / If this is a translation correction or suggestion, please provide the suggested content
validations:
required: false
- type: checkboxes
id: i18n-type
attributes:
label: 问题类型 / Issue Type
description: 请选择适用类型(可多选) / Please select the applicable type(s)
options:
- label: 翻译错误 / Translation error
- label: 翻译缺失 / Missing translation
- label: 建议优化 / Suggestion
- label: 新增语言 / New language
validations:
required: true
- type: input
id: verge-version
attributes:
label: 软件版本 / Verge Version
description: 请提供你使用的 Verge 具体版本 / Please provide the specific version of Verge you are using
validations:
required: true

View File

@@ -1,262 +1,54 @@
name: Alpha Build
on:
# 因为 alpha 不再负责频繁构建,且需要相对于 autobuild 更稳定使用环境
# 所以不再使用 workflow_dispatch 触发
# 应当通过 git tag 来触发构建
# TODO 手动控制版本号
workflow_dispatch:
# inputs:
# tag_name:
# description: "Alpha tag name (e.g. v1.2.3-alpha.1)"
# required: true
# type: string
# push:
# # 应当限制在 dev 分支上触发发布。
# branches:
# - dev
# # 应当限制 v*.*.*-alpha* 的 tag 来触发发布。
# tags:
# - "v*.*.*-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:
TAG_NAME: alpha
TAG_CHANNEL: Alpha
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_alpha_tag:
name: Check Alpha Tag package.json Version Consistency
check_commit:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check tag and package.json version
id: check_tag
run: |
TAG_REF="${GITHUB_REF##*/}"
echo "Current tag: $TAG_REF"
if [[ ! "$TAG_REF" =~ -alpha ]]; then
echo "Current tag is not an alpha tag."
exit 1
fi
PKG_VERSION=$(jq -r .version package.json)
echo "package.json version: $PKG_VERSION"
if [[ "$PKG_VERSION" != *alpha* ]]; then
echo "package.json version is not an alpha version."
exit 1
fi
if [[ "$TAG_REF" != "v$PKG_VERSION" ]]; then
echo "Tag ($TAG_REF) does not match package.json version (v$PKG_VERSION)."
exit 1
fi
echo "Alpha tag and package.json version are consistent."
delete_old_assets:
name: Delete Old Alpha Release Assets and Tags
needs: check_alpha_tag
runs-on: ubuntu-latest
steps:
- name: Delete Old Alpha Tags Except Latest
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tagPattern = /-alpha.*/; // 匹配带有 -alpha 的 tag
const owner = context.repo.owner;
const repo = context.repo.repo;
fetch-depth: 2
try {
// 获取所有 tag
const { data: tags } = await github.rest.repos.listTags({
owner,
repo,
per_page: 100 // 调整 per_page 以获取更多 tag
});
// 过滤出包含 -alpha 的 tag
const alphaTags = (await Promise.all(
tags
.filter(tag => tagPattern.test(tag.name))
.map(async tag => {
// 获取每个 tag 的 commit 信息以获得日期
const { data: commit } = await github.rest.repos.getCommit({
owner,
repo,
ref: tag.commit.sha
});
return {
...tag,
commitDate: commit.committer && commit.committer.date ? commit.committer.date : commit.commit.author.date
};
})
)).sort((a, b) => {
// 按 commit 日期降序排序(最新的在前面)
return new Date(b.commitDate) - new Date(a.commitDate);
});
console.log(`Found ${alphaTags.length} alpha tags`);
if (alphaTags.length === 0) {
console.log('No alpha tags found');
return;
}
// 保留最新的 tag
const latestTag = alphaTags[0];
console.log(`Keeping latest alpha tag: ${latestTag.name}`);
// 处理其他旧的 alpha tag
for (const tag of alphaTags.slice(1)) {
console.log(`Processing tag: ${tag.name}`);
// 获取与 tag 关联的 release
try {
const { data: release } = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: tag.name
});
// 删除 release 下的所有资产
if (release.assets && release.assets.length > 0) {
console.log(`Deleting ${release.assets.length} assets for release ${tag.name}`);
for (const asset of release.assets) {
console.log(`Deleting asset: ${asset.name} (${asset.id})`);
await github.rest.repos.deleteReleaseAsset({
owner,
repo,
asset_id: asset.id
});
}
}
// 删除 release
console.log(`Deleting release for tag: ${tag.name}`);
await github.rest.repos.deleteRelease({
owner,
repo,
release_id: release.id
});
// 删除 tag
console.log(`Deleting tag: ${tag.name}`);
await github.rest.git.deleteRef({
owner,
repo,
ref: `tags/${tag.name}`
});
} catch (error) {
if (error.status === 404) {
console.log(`No release found for tag ${tag.name}, deleting tag directly`);
await github.rest.git.deleteRef({
owner,
repo,
ref: `tags/${tag.name}`
});
} else {
console.error(`Error processing tag ${tag.name}:`, error);
throw error;
}
}
}
console.log('Old alpha tags and releases deleted successfully');
} catch (error) {
console.error('Error:', error);
throw error;
}
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: delete_old_assets
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Fetch UPDATE logs
id: fetch_update_logs
- name: Check if commit changed
id: check
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
shell: bash
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- run: |
if [ -z "$UPDATE_LOGS" ]; then
echo "No update logs found, using default message"
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
else
echo "Using found update logs"
# For manual workflow_dispatch, always run
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
cat > release.txt << EOF
$UPDATE_LOGS
# 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
### MacOS
- MacOS intel芯片: x64.dmg
- 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/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
alpha-x86-windows-macos-linux:
name: Alpha x86 Windows, MacOS and Linux
needs: update_tag
alpha:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
@@ -271,6 +63,7 @@ jobs:
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
@@ -286,13 +79,14 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
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 libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
@@ -307,10 +101,10 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
pnpm check ${{ matrix.target }}
# - name: Release ${{ env.TAG_CHANNEL }} Version
# run: pnpm release-version ${{ env.TAG_NAME }}
- name: Alpha Version update
run: pnpm run fix-alpha-version
- name: Tauri build
uses: tauri-apps/tauri-action@v0
@@ -326,17 +120,17 @@ jobs:
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: ${{ env.TAG_NAME }}
releaseName: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
tagName: alpha
releaseName: "Clash Verge Rev Alpha"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
tauriScript: pnpm
args: --target ${{ matrix.target }}
alpha-arm-linux:
name: Alpha ARM Linux
needs: update_tag
alpha-for-linux-arm:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
@@ -362,7 +156,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
@@ -377,13 +171,10 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
pnpm check ${{ matrix.target }}
# - name: Release ${{ env.TAG_CHANNEL }} Version
# run: pnpm release-version ${{ env.TAG_NAME }}
- name: Setup for linux
run: |
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
cat > /tmp/sources.list << EOF
@@ -402,29 +193,23 @@ jobs:
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt-get update -y
sudo apt-get -f install -y
sudo apt update
sudo apt-get install -y \
linux-libc-dev:${{ matrix.arch }} \
libc6-dev:${{ matrix.arch }}
sudo apt-get install -y \
libxslt1.1:${{ matrix.arch }} \
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
- 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
- name: "Install armv7 tools"
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
@@ -457,17 +242,18 @@ jobs:
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-x86-arm-windows_webview2:
name: Alpha x86 and ARM Windows with WebView2
needs: update_tag
alpha-for-fixed-webview2:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
@@ -490,7 +276,8 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
@@ -505,10 +292,7 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
# - name: Release ${{ env.TAG_CHANNEL }} Version
# run: pnpm release-version ${{ env.TAG_NAME }}
pnpm check ${{ matrix.target }}
- name: Download WebView2 Runtime
run: |
@@ -552,13 +336,77 @@ jobs:
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --${{ env.TAG_NAME }}
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_tag:
name: Update tag
runs-on: ubuntu-latest
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
- name: Set Env
run: |
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 }}
- run: |
cat > release.txt << 'EOF'
## 我应该下载哪个版本?
### MacOS
- MacOS intel芯片: x64.dmg
- 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/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true

View File

@@ -1,466 +0,0 @@
name: Auto Build
on:
workflow_dispatch:
schedule:
# UTC+8 0,6,12,18
- cron: "0 16,22,4,10 * * *"
permissions: write-all
env:
TAG_NAME: autobuild
TAG_CHANNEL: AutoBuild
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
concurrency:
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check_commit:
name: Check Commit Needs Build
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 version changed or src changed
id: check
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
CURRENT_VERSION=$(cat package.json | jq -r '.version')
echo "Current version: $CURRENT_VERSION"
git checkout HEAD~1 package.json
PREVIOUS_VERSION=$(cat package.json | jq -r '.version')
echo "Previous version: $PREVIOUS_VERSION"
git checkout HEAD package.json
if [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then
echo "Version changed from $PREVIOUS_VERSION to $CURRENT_VERSION"
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
CURRENT_SRC_HASH=$(git rev-parse HEAD:src)
PREVIOUS_SRC_HASH=$(git rev-parse HEAD~1:src 2>/dev/null || echo "")
CURRENT_TAURI_HASH=$(git rev-parse HEAD:src-tauri 2>/dev/null || echo "")
PREVIOUS_TAURI_HASH=$(git rev-parse HEAD~1:src-tauri 2>/dev/null || echo "")
echo "Current src hash: $CURRENT_SRC_HASH"
echo "Previous src hash: $PREVIOUS_SRC_HASH"
echo "Current tauri hash: $CURRENT_TAURI_HASH"
echo "Previous tauri hash: $PREVIOUS_TAURI_HASH"
if [ "$CURRENT_SRC_HASH" != "$PREVIOUS_SRC_HASH" ] || [ "$CURRENT_TAURI_HASH" != "$PREVIOUS_TAURI_HASH" ]; then
echo "Source directories changed"
echo "should_run=true" >> $GITHUB_OUTPUT
else
echo "Version and source directories unchanged"
echo "should_run=false" >> $GITHUB_OUTPUT
fi
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "UPDATELOG.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
shell: bash
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- run: |
if [ -z "$UPDATE_LOGS" ]; then
echo "No update logs found, using default message"
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
else
echo "Using found update logs"
fi
cat > release.txt << EOF
$UPDATE_LOGS
## 我应该下载哪个版本?
### MacOS
- MacOS intel芯片: x64.dmg
- 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/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
autobuild-x86-windows-macos-linux:
name: Autobuild x86 Windows, MacOS and Linux
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
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:
- 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
save-if: ${{ github.ref == 'refs/heads/dev' }}
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
- name: Release ${{ env.TAG_CHANNEL }} Version
run: pnpm release-version ${{ env.TAG_NAME }}
- 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:
tagName: ${{ env.TAG_NAME }}
releaseName: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
tauriScript: pnpm
args: --target ${{ matrix.target }}
autobuild-arm-linux:
name: Autobuild ARM Linux
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
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: 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
save-if: ${{ github.ref == 'refs/heads/dev' }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
- name: Release ${{ env.TAG_CHANNEL }} Version
run: pnpm release-version ${{ env.TAG_NAME }}
- 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-get update -y
sudo apt-get -f install -y
sudo apt-get install -y \
linux-libc-dev:${{ matrix.arch }} \
libc6-dev:${{ matrix.arch }}
sudo apt-get install -y \
libxslt1.1:${{ matrix.arch }} \
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
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
autobuild-x86-arm-windows_webview2:
name: Autobuild x86 and ARM Windows with WebView2
needs: update_tag
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
save-if: ${{ github.ref == 'refs/heads/dev' }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
- name: Release ${{ env.TAG_CHANNEL }} Version
run: pnpm release-version ${{ env.TAG_NAME }}
- 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@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: "Clash Verge Rev ${{ env.TAG_CHANNEL }}"
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --${{ env.TAG_NAME }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,63 +0,0 @@
name: Clippy Lint
on:
pull_request:
jobs:
clippy:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
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
save-if: false
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- 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 run prebuild ${{ matrix.target }}
- name: Build Web Assets
run: pnpm run web:build
env:
NODE_OPTIONS: "--max_old_space_size=4096"
- name: Run Clippy
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --all-features -- -D warnings

View File

@@ -1,64 +0,0 @@
name: Cross Platform Cargo Check
on:
workflow_dispatch:
# pull_request:
# push:
# branches: [main, dev]
permissions:
contents: read
jobs:
cargo-check:
# Treat all Rust compiler warnings as errors
env:
RUSTFLAGS: "-D warnings"
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- 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 run prebuild ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
- name: Cargo Check (deny warnings)
working-directory: src-tauri
run: |
cargo check --target ${{ matrix.target }} --workspace --all-features

View File

@@ -42,7 +42,8 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
@@ -57,7 +58,7 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0

View File

@@ -1,50 +0,0 @@
# Copyright 2019-2024 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: Check Formatting
on:
pull_request:
jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: install Rust stable and rustfmt
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: run cargo fmt
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm i -g --force corepack
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
- run: pnpm i --frozen-lockfile
- run: pnpm format:check
# taplo:
# name: taplo (.toml files)
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - name: install Rust stable
# uses: dtolnay/rust-toolchain@stable
# - name: install taplo-cli
# uses: taiki-e/install-action@v2
# with:
# tool: taplo-cli
# - run: taplo fmt --check --diff

View File

@@ -1,16 +1,7 @@
name: Release Build
on:
# ! 为了避免重复发布版本,应当通过独特 git tag 触发。
# ! 不再使用 workflow_dispatch 触发。
# workflow_dispatch:
push:
# 应当限制在 main 分支上触发发布。
branches:
- main
# 应当限制 v*.*.* 的 tag 触发发布。
tags:
- "v*.*.*"
workflow_dispatch:
permissions: write-all
env:
CARGO_INCREMENTAL: 0
@@ -21,28 +12,7 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check_tag_version:
name: Check Release Tag and package.json Version Consistency
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check tag and package.json version
run: |
TAG_REF="${GITHUB_REF##*/}"
echo "Current tag: $TAG_REF"
PKG_VERSION=$(jq -r .version package.json)
echo "package.json version: $PKG_VERSION"
if [[ "$TAG_REF" != "v$PKG_VERSION" ]]; then
echo "Tag ($TAG_REF) does not match package.json version (v$PKG_VERSION)."
exit 1
fi
echo "Tag and package.json version are consistent."
release:
name: Release Build
needs: check_tag_version
strategy:
fail-fast: false
matrix:
@@ -73,13 +43,14 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
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 libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
@@ -94,7 +65,7 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
@@ -117,7 +88,6 @@ jobs:
args: --target ${{ matrix.target }}
release-for-linux-arm:
name: Release Build for Linux ARM
strategy:
fail-fast: false
matrix:
@@ -143,7 +113,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
@@ -158,7 +128,7 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
pnpm check ${{ matrix.target }}
- name: "Setup for linux"
run: |-
@@ -183,7 +153,6 @@ jobs:
sudo apt update
sudo apt install -y \
libxslt1.1:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
@@ -239,7 +208,6 @@ jobs:
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
release-for-fixed-webview2:
name: Release Build for Fixed WebView2
strategy:
fail-fast: false
matrix:
@@ -262,7 +230,8 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
@@ -277,7 +246,7 @@ jobs:
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
pnpm check ${{ matrix.target }}
- name: Download WebView2 Runtime
run: |
@@ -333,7 +302,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update:
name: Release Update
runs-on: ubuntu-latest
needs: [release, release-for-linux-arm]
steps:
@@ -384,7 +352,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
submit-to-winget:
name: Submit to Winget
runs-on: ubuntu-latest
needs: [release-update]
steps:

1
.gitignore vendored
View File

@@ -9,4 +9,3 @@ scripts/_env.sh
.vscode
.tool-versions
.idea
.old

View File

@@ -1,26 +1 @@
#!/bin/bash
#pnpm pretty-quick --staged
if git diff --cached --name-only | grep -q '^src/'; then
pnpm format:check
if [ $? -ne 0 ]; then
echo "Code format check failed in src/. Please fix formatting issues."
exit 1
fi
fi
if git diff --cached --name-only | grep -q '^src-tauri/'; then
cd src-tauri
cargo fmt
if [ $? -ne 0 ]; then
echo "rustfmt failed to format the code. Please fix the issues and try again."
exit 1
fi
cd ..
fi
#git add .
# 允许提交
exit 0
pnpm pretty-quick --staged

View File

@@ -1,27 +0,0 @@
#!/bin/bash
if git diff --cached --name-only | grep -q '^src-tauri/'; then
cargo clippy --manifest-path ./src-tauri/Cargo.toml
if [ $? -ne 0 ]; then
echo "Clippy found issues in src-tauri. Please fix them before pushing."
exit 1
fi
fi
remote_name="$1"
remote_url=$(git remote get-url "$remote_name")
if [[ "$remote_url" =~ github\.com[:/]+clash-verge-rev/clash-verge-rev(\.git)?$ ]]; then
echo "[pre-push] Detected push to clash-verge-rev/clash-verge-rev ($remote_url)"
echo "[pre-push] Running pnpm format:check..."
pnpm format:check
if [ $? -ne 0 ]; then
echo "❌ Code format check failed. Please fix formatting before pushing."
exit 1
fi
else
echo "[pre-push] Not pushing to target repo. Skipping format check."
fi
exit 0

View File

@@ -1,8 +0,0 @@
# README.md
# UPDATELOG.md
# CONTRIBUTING.md
pnpm-lock.yaml
src-tauri/target/
src-tauri/gen/

View File

@@ -1,6 +0,0 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "none",
"experimentalOperatorPosition": "start"
}

View File

@@ -34,27 +34,19 @@ npm install pnpm -g
### Install Dependencies
Install node packages
```shell
pnpm install
```
Install apt packages ONLY for Ubuntu
```shell
apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
```
### Download the Mihomo Core Binary
You have two options for downloading the clash binary:
- Automatically download it via the provided script:
```shell
pnpm run prebuild
pnpm run check
# Use '--force' to force update to the latest version
# pnpm run prebuild --force
# pnpm run check --force
```
- Manually download it from the [Mihomo release](https://github.com/MetaCubeX/mihomo/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
@@ -104,29 +96,6 @@ pnpm portable
## Contributing Your Changes
#### Before commit your changes
If you changed the rust code, it's recommanded to execute code style formatting and quailty checks.
1. Code quailty checks
```bash
# For rust backend
$ clash-verge-rev: pnpm clippy
# For frontend (not yet).
```
2. Code style formatting
```bash
# For rust backend
$ clash-verge-rev: cd src-tauri
$ clash-verge-rev/src-tauri: cargo fmt
# For frontend
$ clash-verge-rev: pnpm format:check
$ clash-verge-rev: pnpm format
```
Once you have made your changes:
1. Fork the repository.

View File

@@ -18,18 +18,10 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
## Install
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
Go to the [Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
Go to the [release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
#### 我应当怎样选择发行版
| 版本 | 特征 | 链接 |
| :-------- | :--------------------------------------- | :------------------------------------------------------------------------------------- |
| Stable | 正式版,高可靠性,适合日常使用。 | [Release](https://github.com/clash-verge-rev/clash-verge-rev/releases) |
| Alpha | 早期测试版,功能未完善,可能存在缺陷。 | [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) |
| AutoBuild | 滚动更新版,持续集成更新,适合开发测试。 | [AutoBuild](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/autobuild) |
#### 安装说明和常见问题,请到 [文档页](https://clash-verge-rev.github.io/) 查看
### 安装说明和常见问题,请到[文档页](https://clash-verge-rev.github.io/)查看:[Doc](https://clash-verge-rev.github.io/)
---
@@ -37,7 +29,7 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
## Promotion
#### [狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
[狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
@@ -49,19 +41,6 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
- 解锁流媒体及 ChatGPT
- 官网:[https://狗狗加速.com](https://verge.dginv.click/#/register?code=oaxsAGo6)
#### 本项目的构建与发布环境由 [YXVM](https://yxvm.com/aff.php?aff=827) 独立服务器全力支持,
感谢提供 独享资源、高性能、高速网络 的强大后端环境。如果你觉得下载够快、使用够爽,那是因为我们用了好服务器!
🧩 YXVM 独立服务器优势:
- 🌎 优质网络,回程优化,下载快到飞起
- 🔧 物理机独享资源非VPS可比性能拉满
- 🧠 适合跑代理、搭建 WEB 站 CDN 站 、搞 CI/CD 或任何高负载应用
- 💡 支持即开即用多机房选择CN2 / IEPL 可选
- 📦 本项目使用配置已在售,欢迎同款入手!
- 🎯 想要同款构建体验?[立即下单 YXVM 独立服务器!](https://yxvm.com/aff.php?aff=827)
## Features
- 基于性能强劲的 Rust 和 Tauri 2 框架

View File

@@ -1,337 +1,3 @@
## v2.3.1
### 🐞 修复问题
- 增加配置文件校验,修复从古老版本升级上来的"No such file or directory (os error 2)"错误
- 修复扩展脚本转义错误
- 修复 macOS Intel X86 架构构建错误导致无法运行
- 修复 Linux 下界面边框白边问题
- 修复 托盘 无响应问题
- 修复 托盘 无法从轻量模式退出并恢复窗口
- 修复 快速切换订阅可能导致的卡死问题
### ✨ 新增功能
- 新增 window-state 窗口状态管理和恢复
### 🚀 优化改进
- 优化 托盘 统一响应
- 优化 静默启动+自启动轻量模式 运行方式
- 升级依赖
## v2.3.0
**发行代号:御**
代号释义: 「御」,象征掌控与守护,寓意本次版本对系统稳定性、安全性与用户体验的全面驾驭与提升。
尽管 `external-controller` 密钥现已自动补全默认值且不允许为空,**仍建议手动修改密钥以提高安全性**。
### ⚠️ 已知问题
- 仅在 Ubuntu 22.04/24.04、Fedora 41 的 **GNOME 桌面环境** 做过简单测试,不保证其他 Linux 发行版兼容,后续将逐步适配和优化。
- macOS
- MacOS 下自动升级成功后请关闭程序等待 30 秒重启,因为 MacOS 的端口释放特性,卸载服务后需重启应用等 30 秒才能恢复内核通信。立即启动可能无法正常启动内核。
- 墙贴主要为浅色,深色 Tray 图标存在闪烁问题;
- 彩色 Tray 图标颜色偏淡;
- 已确认窗口状态管理器存在上游缺陷,已暂时移除窗口大小与位置记忆功能。
### 🐞 修复问题
- 修复首页“代理模式”快速切换导致的卡死问题
- 修复 MacOS 快捷键关闭窗口无法启用自动轻量模式
- 修复静默启动异常窗口的创建与关闭流程
- 修复 Windows 下错误注册的全局快捷键 `Ctrl+Q`
- 修复解锁测试报错信息与 VLESS URL 解码时的网络类型错误
- 修复切换自定义代理地址后系统代理状态异常
- 修复 macOS TUN 默认无效网卡名称
- 修复更改订阅后托盘 UI 不同步的问题
- 修复服务模式安装后无法立即开启 TUN 模式
- 修复无法删除 `.window-state.json`
- 修复无法修改配置更新 HTTP 请求超时问题
- 修复 `getDelayFix` 钩子异常
- 修复外部扩展脚本覆写代理组时首页无法显示代理组
- 修复 Verge 导出诊断版本与设置页面不同步
- 修复切换语言时设置页面可能加载失败
- 修复编辑器中连字符处理问题
- 修复提权漏洞,改用带认证的 IPC 通信机制
- 修复静默启动无法使用自动轻量模式
- 修复 JS 脚本转义特殊字符报错
- 修复 macOS 静默启动时异常启动 Dock 栏图标
### ✨ 新增功能
- **Mihomo(Meta) 内核升级至 v1.19.10**
- 支持设置代理地址为非 `127.0.0.1`,提升 WSL 兼容性
- 系统代理守卫:可检测意外变更并自动恢复
- 托盘新增当前轻量模式状态显示
- 关闭系统代理时同时断开已建立的连接
- 新增 WebDAV 功能:
- 加入 UA 请求头
- 支持目录重定向
- 备份目录检查与上传重试机制
- 自动订阅更新机制:
- 加入请求超时机制防止卡死
- 支持在代理状态下自动重试订阅更新
- 支持订阅卡片点击切换下次自动更新时间,并显示更新结果提示
- DNS 设置新增 Hosts 配置功能
- 首页代理节点支持排序
- 支持服务模式手动卸载,回退至 Sidecar 模式
- 核心状态管理支持切换、升级、重启
- 配置加载阶段自动补全 `external-controller secret`
- 新增日志自动清理周期选项含1天
- 新增 Zashboard 一键跳转入口
- 使用系统默认窗口管理器
### 🚀 优化改进
- **系统相关:**
- 系统代理 Bypass 设置优化
- 优化代理设置更新逻辑与守卫机制
- Windows 启动方式调整为 Startup 文件夹,解决管理员模式下自启问题
- **性能与稳定性:**
- 全面异步化处理配置加载、UI 启动、事件通知等关键流程,解决卡顿问题
- 优化 MihomoManager 实现与窗口创建流程
- 改进内核日志等级为 `warn`,减少噪音输出
- 重构主进程与通知系统,提升响应性与分离度
- 优化网络请求与错误处理机制
- 添加网络管理器防止资源竞争引发 UI 卡死
- 优化配置文件加载内存使用
- 优化缓存 Mihomo proxy 和 providers 信息内存使用
- **前端与界面体验:**
- 切换规则页自动刷新数据
- 非激活订阅编辑时不再触发配置重载
- 优化托盘速率显示macOS 下默认关闭
- Windows 快捷键名称更名为 `Clash Verge`
- 更新失败可回退至使用代理重试
- 支持异步端口查找与保存,端口支持随机生成
- 修改端口检测范围至 `1111-65536`
- 优化保存机制,使用平滑函数防止卡顿
- **配置增强与安全性:**
- 配置缺失 `secret` 字段时自动补全为 `set-your-secret`
- 强制为 Mihomo 配置补全 `external-controller-cors` 字段(默认不允许跨域,限制本地访问)计划后续支持自定义 cors
- 优化窗口权限设置与状态初始化逻辑
- 网络延迟测试替换为 HTTPS 协议:`https://cp.cloudflare.com/generate_204`
- 优化 IP 信息获取流程,添加去重机制与轮询检测算法
- 同步修复翻译错误与不一致项,优化整体语言体验
- 加强语言切换后的页面稳定性,避免加载异常
### 🗑️ 移除内容
- 窗口状态管理器(上游存在缺陷)
- WebDAV 跨平台备份恢复限制
---
## v2.2.3
#### 已知问题
- 仅在Ubuntu 22.04/24.04Fedora 41 **Gnome桌面环境** 做过简单测试不保证其他其他Linux发行版可用将在未来做进一步适配和调优
- MacOS 自定义图标与速率显示推荐图标尺寸为 256x256。其他尺寸可能会导致不正常图标和速率间隙
- MacOS 下 墙贴主要为浅色Tray 图标深色时图标闪烁;彩色 Tray 速率颜色淡
- Linux 下 Clash Verge Rev 内存占用显著高于 Windows / MacOS
### 2.2.3 相对于 2.2.2
#### 修复了:
- 首页“当前代理”因为重复刷新导致的CPU占用过高的问题
- “开机自启”和“DNS覆写”开关跳动问题
- 自定义托盘图标未能应用更改
- MacOS 自定义托盘图标显示速率时图标和文本间隙过大
- MacOS 托盘速率显示不全
- Linux 在系统服务模式下无法拉起 Mihomo 内核
- 使用异步操作,避免获取系统信息和切换代理模式可能带来的崩溃
- 相同节点名称可能导致的页面渲染出错
- URL Schemes被截断的问题
- 首页流量统计卡更好的时间戳范围
- 静默启动无法触发自动轻量化计时器
#### 新增了:
- Mihomo(Meta)内核升级至 1.19.4
- Clash Verge Rev 从现在开始不再强依赖系统服务和管理权限
- 支持根据用户偏好选择Sidecar(用户空间)模式或安装服务
- 增加载入初始配置文件的错误提示,防止切换到错误的订阅配置
- 检测是否以管理员模式运行软件,如果是提示无法使用开机自启
- 代理组显示节点数量
- 统一运行模式检测支持管理员模式下开启TUN模式
- 托盘切换代理模式会根据设置自动断开之前连接
- 如订阅获取失败回退使用Clash内核代理再次尝试
#### 移除了:
- 实时保存窗口位置和大小。这个功能可能会导致窗口异常大小和位置,还需观察。
#### 优化了:
- 重构了后端内核管理逻辑,更轻量化和有效的管理内核,提高了性能和稳定性
- 前端统一刷新应用数据,优化数据获取和刷新逻辑
- 优化首页流量图表代码,调整图表文字边距
- MacOS 托盘速率更好的显示样式和更新逻辑
- 首页仅在有流量图表时显示流量图表区域
- 更新DNS默认覆写配置
- 移除测试目录,简化资源初始化逻辑
## v2.2.2
**发行代号:拓**
感谢 Tunglies 对 Verge 后端重构,性能优化做出的重大贡献!
代号释义: 本次发布在功能上的大幅扩展。新首页设计为用户带来全新交互体验DNS 覆写功能增强网络控制能力解锁测试页面助力内容访问自由度提升轻量模式提供灵活使用选择。此外macOS 应用菜单集成、sidecar 模式、诊断信息导出等新特性进一步丰富了软件的适用场景。这些新增功能显著拓宽了 Clash Verge 的功能边界,为用户提供了更强大的工具和可能性。
#### 已知问题
- 仅在Ubuntu 22.04/24.04Fedora 41 **Gnome桌面环境** 做过简单测试不保证其他其他Linux发行版可用将在未来做进一步适配和调优
### 2.2.2 相对于 2.2.1(已下架不再提供)
#### 修复了:
- 弹黑框的问题(原因是服务崩溃触发重装机制)
- MacOS进入轻量模式以后隐藏Dock图标
- 增加轻量模式缺失的tray翻译
- Linux下的窗口边框被削掉的问题
#### 新增了:
- 加强服务检测和重装逻辑
- 增强内核与服务保活机制
- 增加服务模式下的僵尸进程清理机制
- 新增当服务模式多次尝试失败后自动回退至用户空间模式
### 2.2.1 相对于 2.2.0(已下架不再提供)
#### 修复了:
1. **首页**
- 修复 Direct 模式首页无法渲染
- 修复 首页启用轻量模式导致 ClashVergeRev 从托盘退出
- 修复 系统代理标识判断不准的问题
- 修复 系统代理地址错误的问题
- 代理模式“多余的切换动画”
2. **系统**
- 修复 MacOS 无法使用快捷键粘贴/选择/复制订阅地址。
- 修复 代理端口设置同步问题。
- 修复 Linux 无法与 Mihomo 核心 和 ClashVergeRev 服务通信
3. **界面**
- 修复 连接详情卡没有跟随主题色
4. **轻量模式**
- 修复 MacOS 轻量模式下 Dock 栏图标无法隐藏。
#### 新增了:
1. **首页**
- 首页文本过长自动截断
2. **轻量模式**
- 新增托盘进入轻量模式支持
- 新增进入轻量模式快捷键支持
3. **系统**
- 在 ClashVergeRev 对 Mihomo 进行操作时,总是尝试确保两者运行
- 服务器模式下启动mihomo内核的时候查找并停止其他已经存在的内核进程防止内核假死等问题带来的通信失败
4. **托盘**
- 新增 MacOS 启用托盘速率显示时,可选隐藏托盘图标显示
---
## 2.2.0(已下架不再提供)
#### 新增功能
1. **首页**
- 新增首页功能,默认启动页面改为首页。
- 首页流量图卡片显示上传/下载名称。
- 首页支持轻量模式切换。
- 流量统计数据持久保存。
- 限制首页配置文件卡片URL长度。
2. **DNS 设置与覆写**
- 新增 DNS 覆写功能。
- 默认启用 DNS 覆写。
3. **解锁测试**
- 新增解锁测试页面。
4. **轻量模式**
- 新增轻量模式及设置。
- 添加自动轻量模式定时器。
5. **系统支持**
- Mihomo(meta)内核升级 1.19.3
- macOS 支持 CMD+W 关闭窗口。
- 新增 macOS 应用菜单。
- 添加 macOS 安装服务时候的管理员权限提示。
- 新增 sidecar(用户空间启动内核) 模式。
6. **其他**
- 增强延迟测试日志和错误处理。
- 添加诊断信息导出。
- 新增代理命令。
#### 修复
1. **系统**
- 修复 Windows 热键崩溃。
- 修复 macOS 无框标题。
- 修复 macOS 静默启动崩溃。
- 修复 macOS tray图标错位到左上角的问题。
- 修复 Windows/Linux 运行时崩溃。
- 修复 Win10 阴影和边框问题。
- 修复 升级或重装后开机自启状态检测和同步问题。
2. **构建**
- 修复构建失败问题。
#### 优化
1. **性能**
- 重构后端,巨幅性能优化。
- 优化首页组件性能。
- 优化流量图表资源使用。
- 提升代理组列表滚动性能。
- 加快应用退出速度。
- 加快进入轻量模式速度。
- 优化小数值速度更新。
- 增加请求超时至 60 秒。
- 修复代理节点选择同步。
- 优化修改verge配置性能。
2. **重构**
- 重构后端,巨幅性能优化。
- 优化定时器管理。
- 重构 MihomoManager 处理流量。
- 优化 WebSocket 连接。
3. **其他**
- 更新依赖。
- 默认 TUN 堆栈改为 gvisor。
---
## v2.1.2
**发行代号:臻**
@@ -340,14 +6,14 @@
感谢 Tychristine 对社区群组管理做出的重大贡献!
##### 2.1.2相对2.1.1(已下架不提供)更新了:
##### 2.1.2相对2.1.1(已下架不提供)更新了:
- 无法更新和签名验证失败的问题(该死的CDN缓存)
- 设置菜单区分Verge基本设置和高级设置
- 增加v2 Updater的更多功能和权限
- 退出Verge后Tun代理状态仍保留的问题
##### 2.1.1相对2.1.0(已下架不提供)更新了:
##### 2.1.1相对2.1.0(已下架不提供)更新了:
- 检测所需的Clash Verge Service版本杀毒软件误报可能与此有关因为检测和安装新版本Service需管理员权限
- MacOS下支持彩色托盘图标和更好速率显示感谢Tunglies

View File

@@ -1,4 +0,0 @@
files:
- source: /src/locales/en.json
translation: /src/locales
multilingual: 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -1,28 +1,24 @@
{
"name": "clash-verge",
"version": "2.3.1",
"version": "2.1.3-alpha",
"license": "GPL-3.0-only",
"scripts": {
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
"dev:diff": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
"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 --noEmit && vite build",
"web:serve": "vite preview",
"prebuild": "node scripts/prebuild.mjs",
"check": "node scripts/check.mjs",
"updater": "node scripts/updater.mjs",
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
"portable": "node scripts/portable.mjs",
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
"fix-alpha-version": "node scripts/fix-alpha_version.mjs",
"release-version": "node scripts/release-version.mjs",
"publish-version": "node scripts/publish-version.mjs",
"fmt": "cargo fmt --manifest-path ./src-tauri/Cargo.toml",
"clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml",
"format": "prettier --write .",
"format:check": "prettier --check ."
"fix-alpha-version": "node scripts/alpha_version.mjs",
"prepare": "husky",
"clean": "cd ./src-tauri && cargo clean && cd -"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@@ -31,77 +27,73 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^7.1.1",
"@mui/lab": "7.0.0-beta.13",
"@mui/material": "^7.1.1",
"@mui/x-data-grid": "^8.5.2",
"@tauri-apps/api": "2.5.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.3",
"@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "^2.3.0",
"@tauri-apps/plugin-global-shortcut": "^2.2.1",
"@tauri-apps/plugin-notification": "^2.2.3",
"@tauri-apps/plugin-process": "^2.2.2",
"@tauri-apps/plugin-shell": "2.2.2",
"@tauri-apps/plugin-updater": "2.8.1",
"@tauri-apps/plugin-window-state": "^2.2.3",
"@types/d3-shape": "^3.1.7",
"@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.5",
"axios": "^1.10.0",
"chart.js": "^4.5.0",
"ahooks": "^3.8.4",
"axios": "^1.7.9",
"cli-color": "^2.0.4",
"d3-shape": "^3.2.0",
"dayjs": "1.11.13",
"foxact": "^0.2.49",
"glob": "^11.0.3",
"i18next": "^25.2.1",
"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.52.2",
"monaco-yaml": "^5.4.0",
"nanoid": "^5.1.5",
"peggy": "^5.0.3",
"react": "19.1.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "19.1.0",
"react-error-boundary": "6.0.0",
"react-hook-form": "^7.58.1",
"react-i18next": "15.5.3",
"react-markdown": "10.1.0",
"react-monaco-editor": "0.58.0",
"react-router-dom": "7.6.2",
"react-virtuoso": "^4.13.0",
"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-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.3",
"sockette": "^2.0.6",
"swr": "^2.3.3",
"swr": "^2.3.0",
"tar": "^7.4.3",
"types-pac": "^1.0.3",
"zustand": "^5.0.5"
"zustand": "^5.0.3"
},
"devDependencies": {
"@actions/github": "^6.0.1",
"@tauri-apps/cli": "2.5.0",
"@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": "19.1.8",
"@types/react-dom": "19.1.6",
"@vitejs/plugin-legacy": "^6.1.1",
"@vitejs/plugin-react": "4.5.2",
"@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.16",
"commander": "^14.0.0",
"cross-env": "^7.0.3",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"meta-json-schema": "^1.19.10",
"meta-json-schema": "^1.19.1",
"node-fetch": "^3.3.2",
"prettier": "^3.5.3",
"pretty-quick": "^4.2.2",
"sass": "^1.89.2",
"terser": "^5.43.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"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.3.0"
},

8047
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
{
"extends": ["config:recommended"],
"baseBranches": ["dev"],
"enabledManagers": ["cargo", "npm"],
"labels": ["dependencies"],
"ignorePaths": [
"**/node_modules/**",
"**/bower_components/**",
"**/vendor/**",
"**/__tests__/**",
"**/test/**",
"**/tests/**",
"**/__fixtures__/**",
"**/crate/**",
"shared/**"
],
"rangeStrategy": "bump",
"packageRules": [
{
"semanticCommitType": "chore",
"matchPackageNames": ["*"]
},
{
"description": "Disable node/pnpm version updates",
"matchPackageNames": ["node", "pnpm"],
"matchDepTypes": ["engines", "packageManager"],
"enabled": false
},
{
"description": "Group all cargo dependencies into a single PR",
"matchManagers": ["cargo"],
"groupName": "cargo dependencies"
},
{
"description": "Group all npm dependencies into a single PR",
"matchManagers": ["npm"],
"groupName": "npm dependencies"
}
],
"postUpdateOptions": ["pnpmDedupe"],
"ignoreDeps": ["serde_yaml"]
}

View File

@@ -38,17 +38,6 @@ async function updatePackageVersion(newVersion) {
const packageJson = JSON.parse(data);
// 获取键值替换
let result = packageJson.version.replace("alpha", newVersion);
// 检查当前版本号是否已经包含了 alpha- 后缀
if (!packageJson.version.includes(`alpha-`)) {
// 如果只有 alpha 而没有 alpha-,则替换为 alpha-newVersion
result = packageJson.version.replace("alpha", `alpha-${newVersion}`);
} else {
// 如果已经是 alpha-xxx 格式,则更新 xxx 部分
result = packageJson.version.replace(
/alpha-[^-]*/,
`alpha-${newVersion}`,
);
}
console.log("[INFO]: Current version is: ", result);
packageJson.version = result;
// 写入版本号

View File

@@ -1,102 +0,0 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const LOCALES_DIR = path.resolve(__dirname, "../src/locales");
const SRC_DIRS = [
path.resolve(__dirname, "../src"),
path.resolve(__dirname, "../src-tauri"),
];
const exts = [".js", ".ts", ".tsx", ".jsx", ".vue", ".rs"];
// 递归获取所有文件
function getAllFiles(dir, exts) {
let files = [];
fs.readdirSync(dir).forEach((file) => {
const full = path.join(dir, file);
if (fs.statSync(full).isDirectory()) {
files = files.concat(getAllFiles(full, exts));
} else if (exts.includes(path.extname(full))) {
files.push(full);
}
});
return files;
}
// 读取所有源码内容为一个大字符串
function getAllSourceContent() {
const files = SRC_DIRS.flatMap((dir) => getAllFiles(dir, exts));
return files.map((f) => fs.readFileSync(f, "utf8")).join("\n");
}
// 白名单 key不检查这些 key 是否被使用
const WHITELIST_KEYS = [
"theme.light",
"theme.dark",
"theme.system",
"Already Using Latest Core Version",
];
// 主流程
function processI18nFile(i18nPath, lang, allSource) {
const i18n = JSON.parse(fs.readFileSync(i18nPath, "utf8"));
const keys = Object.keys(i18n);
const used = {};
const unused = [];
let checked = 0;
const total = keys.length;
keys.forEach((key) => {
if (WHITELIST_KEYS.includes(key)) {
used[key] = i18n[key];
} else {
// 只查找一次
const regex = new RegExp(`["'\`]${key}["'\`]`);
if (regex.test(allSource)) {
used[key] = i18n[key];
} else {
unused.push(key);
}
}
checked++;
if (checked % 20 === 0 || checked === total) {
const percent = ((checked / total) * 100).toFixed(1);
process.stdout.write(
`\r[${lang}] Progress: ${checked}/${total} (${percent}%)`,
);
if (checked === total) process.stdout.write("\n");
}
});
// 输出未使用的 key
console.log(`\n[${lang}] Unused keys:`, unused);
// 备份原文件
const oldPath = i18nPath + ".old";
fs.renameSync(i18nPath, oldPath);
// 写入精简后的 i18n 文件(保留原文件名)
fs.writeFileSync(i18nPath, JSON.stringify(used, null, 2), "utf8");
console.log(
`[${lang}] Cleaned i18n file written to src/locales/${path.basename(i18nPath)}`,
);
console.log(`[${lang}] Original file backed up as ${path.basename(oldPath)}`);
}
function main() {
// 支持 zhtw.json、zh-tw.json、zh_CN.json 等
const files = fs
.readdirSync(LOCALES_DIR)
.filter((f) => /^[a-z0-9\-_]+\.json$/i.test(f) && !f.endsWith(".old"));
const allSource = getAllSourceContent();
files.forEach((file) => {
const lang = path.basename(file, ".json");
processI18nFile(path.join(LOCALES_DIR, file), lang, allSource);
});
}
main();

View File

@@ -1,66 +0,0 @@
// scripts/publish-version.mjs
import { spawn } from "child_process";
import { existsSync } from "fs";
import path from "path";
const rootDir = process.cwd();
const scriptPath = path.join(rootDir, "scripts", "release-version.mjs");
if (!existsSync(scriptPath)) {
console.error("release-version.mjs not found!");
process.exit(1);
}
const versionArg = process.argv[2];
if (!versionArg) {
console.error("Usage: pnpm publish-version <version>");
process.exit(1);
}
// 1. 调用 release-version.mjs
const runRelease = () =>
new Promise((resolve, reject) => {
const child = spawn("node", [scriptPath, versionArg], { stdio: "inherit" });
child.on("exit", (code) => {
if (code === 0) resolve();
else reject(new Error("release-version failed"));
});
});
// 2. 判断是否需要打 tag
function isSemver(version) {
return /^v?\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$/.test(version);
}
async function run() {
await runRelease();
let tag = null;
if (versionArg === "alpha") {
// 读取 package.json 里的主版本
const pkg = await import(path.join(rootDir, "package.json"), {
assert: { type: "json" },
});
tag = `v${pkg.default.version}-alpha`;
} else if (isSemver(versionArg)) {
// 1.2.3 或 v1.2.3
tag = versionArg.startsWith("v") ? versionArg : `v${versionArg}`;
}
if (tag) {
// 打 tag 并推送
const { execSync } = await import("child_process");
try {
execSync(`git tag ${tag}`, { stdio: "inherit" });
execSync(`git push origin ${tag}`, { stdio: "inherit" });
console.log(`[INFO]: Git tag ${tag} created and pushed.`);
} catch (e) {
console.error(`[ERROR]: Failed to create or push git tag: ${tag}`);
process.exit(1);
}
} else {
console.log("[INFO]: No git tag created for this version.");
}
}
run();

View File

@@ -1,253 +0,0 @@
/**
* CLI tool to update version numbers in package.json, src-tauri/Cargo.toml, and src-tauri/tauri.conf.json.
*
* Usage:
* pnpm release-version <version>
*
* <version> can be:
* - A full semver version (e.g., 1.2.3, v1.2.3, 1.2.3-beta, v1.2.3+build)
* - A tag: "alpha", "beta", "rc", or "autobuild"
* - "alpha", "beta", "rc": Appends the tag to the current base version (e.g., 1.2.3-beta)
* - "autobuild": Appends a timestamped autobuild tag (e.g., 1.2.3+autobuild.2406101530)
*
* Examples:
* pnpm release-version 1.2.3
* pnpm release-version v1.2.3-beta
* pnpm release-version beta
* pnpm release-version autobuild
*
* The script will:
* - Validate and normalize the version argument
* - Update the version field in package.json
* - Update the version field in src-tauri/Cargo.toml
* - Update the version field in src-tauri/tauri.conf.json
*
* Errors are logged and the process exits with code 1 on failure.
*/
import fs from "fs/promises";
import path from "path";
import { program } from "commander";
import { execSync } from "child_process";
/**
* 获取当前 git 短 commit hash
* @returns {string}
*/
function getGitShortCommit() {
try {
return execSync("git rev-parse --short HEAD").toString().trim();
} catch (e) {
console.warn("[WARN]: Failed to get git short commit, fallback to 'nogit'");
return "nogit";
}
}
/**
* 生成短时间戳格式YYMMDD或带 commit格式YYMMDD.cc39b27
* @param {boolean} withCommit 是否带 commit
* @returns {string}
*/
function generateShortTimestamp(withCommit = false) {
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
if (withCommit) {
const gitShort = getGitShortCommit();
return `${month}${day}.${gitShort}`;
}
return `${month}${day}`;
}
/**
* 验证版本号格式
* @param {string} version
* @returns {boolean}
*/
function isValidVersion(version) {
return /^v?\d+\.\d+\.\d+(-(alpha|beta|rc)(\.\d+)?)?(\+[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*)?$/i.test(
version,
);
}
/**
* 标准化版本号
* @param {string} version
* @returns {string}
*/
function normalizeVersion(version) {
return version.startsWith("v") ? version : `v${version}`;
}
/**
* 提取基础版本号(去掉所有 -tag 和 +build 部分)
* @param {string} version
* @returns {string}
*/
function getBaseVersion(version) {
let base = version.replace(/-(alpha|beta|rc)(\.\d+)?/i, "");
base = base.replace(/\+[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*/g, "");
return base;
}
/**
* 更新 package.json 版本号
* @param {string} newVersion
*/
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);
console.log(
"[INFO]: Current package.json version is: ",
packageJson.version,
);
packageJson.version = newVersion.startsWith("v")
? newVersion.slice(1)
: newVersion;
await fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
"utf8",
);
console.log(
`[INFO]: package.json version updated to: ${packageJson.version}`,
);
} catch (error) {
console.error("Error updating package.json version:", error);
throw error;
}
}
/**
* 更新 Cargo.toml 版本号
* @param {string} newVersion
*/
async function updateCargoVersion(newVersion) {
const _dirname = process.cwd();
const cargoTomlPath = path.join(_dirname, "src-tauri", "Cargo.toml");
try {
const data = await fs.readFile(cargoTomlPath, "utf8");
const lines = data.split("\n");
const versionWithoutV = newVersion.startsWith("v")
? newVersion.slice(1)
: newVersion;
const baseVersion = getBaseVersion(versionWithoutV);
const updatedLines = lines.map((line) => {
if (line.trim().startsWith("version =")) {
return line.replace(
/version\s*=\s*"[^"]+"/,
`version = "${baseVersion}"`,
);
}
return line;
});
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
console.log(`[INFO]: Cargo.toml version updated to: ${baseVersion}`);
} catch (error) {
console.error("Error updating Cargo.toml version:", error);
throw error;
}
}
/**
* 更新 tauri.conf.json 版本号
* @param {string} newVersion
*/
async function updateTauriConfigVersion(newVersion) {
const _dirname = process.cwd();
const tauriConfigPath = path.join(_dirname, "src-tauri", "tauri.conf.json");
try {
const data = await fs.readFile(tauriConfigPath, "utf8");
const tauriConfig = JSON.parse(data);
const versionWithoutV = newVersion.startsWith("v")
? newVersion.slice(1)
: newVersion;
const baseVersion = getBaseVersion(versionWithoutV);
console.log(
"[INFO]: Current tauri.conf.json version is: ",
tauriConfig.version,
);
tauriConfig.version = baseVersion;
await fs.writeFile(
tauriConfigPath,
JSON.stringify(tauriConfig, null, 2),
"utf8",
);
console.log(`[INFO]: tauri.conf.json version updated to: ${baseVersion}`);
} catch (error) {
console.error("Error updating tauri.conf.json version:", error);
throw error;
}
}
/**
* 获取当前版本号
*/
async function getCurrentVersion() {
const _dirname = process.cwd();
const packageJsonPath = path.join(_dirname, "package.json");
try {
const data = await fs.readFile(packageJsonPath, "utf8");
const packageJson = JSON.parse(data);
return packageJson.version;
} catch (error) {
console.error("Error getting current version:", error);
throw error;
}
}
/**
* 主函数
*/
async function main(versionArg) {
if (!versionArg) {
console.error("Error: Version argument is required");
process.exit(1);
}
try {
let newVersion;
const validTags = ["alpha", "beta", "rc", "autobuild"];
if (validTags.includes(versionArg.toLowerCase())) {
const currentVersion = await getCurrentVersion();
const baseVersion = getBaseVersion(currentVersion);
if (versionArg.toLowerCase() === "autobuild") {
// 格式: 2.3.0+autobuild.250613.cc39b27
newVersion = `${baseVersion}+autobuild.${generateShortTimestamp(true)}`;
} else {
newVersion = `${baseVersion}-${versionArg.toLowerCase()}`;
}
} else {
if (!isValidVersion(versionArg)) {
console.error("Error: Invalid version format");
process.exit(1);
}
newVersion = normalizeVersion(versionArg);
}
console.log(`[INFO]: Updating versions to: ${newVersion}`);
await updatePackageVersion(newVersion);
await updateCargoVersion(newVersion);
await updateTauriConfigVersion(newVersion);
console.log("[SUCCESS]: All version updates completed successfully!");
} catch (error) {
console.error("[ERROR]: Failed to update versions:", error);
process.exit(1);
}
}
program
.name("pnpm release-version")
.description("Update project version numbers")
.argument("<version>", "version tag or full version")
.action(main)
.parse(process.argv);

View File

@@ -43,42 +43,3 @@ export async function resolveUpdateLog(tag) {
return map[tag].join("\n").trim();
}
export async function resolveUpdateLogDefault() {
const cwd = process.cwd();
const file = path.join(cwd, UPDATE_LOG);
if (!fs.existsSync(file)) {
throw new Error("could not found UPDATELOG.md");
}
const data = await fsp.readFile(file, "utf-8");
const reTitle = /^## v[\d\.]+/;
const reEnd = /^---/;
let isCapturing = false;
let content = [];
let firstTag = "";
for (const line of data.split("\n")) {
if (reTitle.test(line) && !isCapturing) {
isCapturing = true;
firstTag = line.slice(3).trim();
continue;
}
if (isCapturing) {
if (reEnd.test(line)) {
break;
}
content.push(line);
}
}
if (!firstTag) {
throw new Error("could not found any version tag in UPDATELOG.md");
}
return content.join("\n").trim();
}

View File

@@ -1,6 +1,6 @@
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
import { resolveUpdateLog } from "./updatelog.mjs";
// Add stable update JSON filenames
const UPDATE_TAG_NAME = "updater";
@@ -8,8 +8,8 @@ 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.json";
const ALPHA_UPDATE_JSON_PROXY = "update-proxy.json";
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
@@ -78,235 +78,192 @@ async function resolveUpdater() {
async function processRelease(github, options, tag, isAlpha) {
if (!tag) return;
try {
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name).catch(() =>
resolveUpdateLogDefault().catch(() => "No changelog available"),
),
pub_date: new Date().toISOString(),
platforms: {
win64: { signature: "", url: "" }, // compatible with older formats
linux: { signature: "", url: "" }, // compatible with older formats
darwin: { signature: "", url: "" }, // compatible with older formats
"darwin-aarch64": { signature: "", url: "" },
"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 updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name).catch(
() => "No changelog available",
),
pub_date: new Date().toISOString(),
platforms: {
win64: { signature: "", url: "" }, // compatible with older formats
linux: { signature: "", url: "" }, // compatible with older formats
darwin: { signature: "", url: "" }, // compatible with older formats
"darwin-aarch64": { signature: "", url: "" },
"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 = release.assets.map(async (asset) => {
const { name, browser_download_url } = 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.exe")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
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.exe")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
// darwin url (intel)
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
updateData.platforms.darwin.url = browser_download_url;
updateData.platforms["darwin-intel"].url = browser_download_url;
updateData.platforms["darwin-x86_64"].url = browser_download_url;
}
// darwin signature (intel)
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.darwin.signature = sig;
updateData.platforms["darwin-intel"].signature = sig;
updateData.platforms["darwin-x86_64"].signature = sig;
}
// 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;
updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig;
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;
}
});
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];
}
});
// 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://download.clashverge.dev/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// 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,
);
try {
let updateRelease;
try {
// Try to get the existing release
const response = await github.rest.repos.getReleaseByTag({
...options,
tag: releaseTag,
});
updateRelease = response.data;
console.log(
`Found existing ${releaseTag} release with ID: ${updateRelease.id}`,
);
} catch (error) {
// If release doesn't exist, create it
if (error.status === 404) {
console.log(
`Release with tag ${releaseTag} not found, creating new release...`,
);
const createResponse = await github.rest.repos.createRelease({
...options,
tag_name: releaseTag,
name: isAlpha
? "Auto-update Alpha Channel"
: "Auto-update Stable Channel",
body: `This release contains the update information for ${isAlpha ? "alpha" : "stable"} channel.`,
prerelease: isAlpha,
});
updateRelease = createResponse.data;
console.log(
`Created new ${releaseTag} release with ID: ${updateRelease.id}`,
);
} else {
// If it's another error, throw it
throw error;
}
}
// 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
}
}
// 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,
);
// Process all the platform URL and signature data
// win64 url
if (name.endsWith("x64-setup.exe")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
} catch (error) {
if (error.status === 404) {
console.log(`Release not found for tag: ${tag.name}, skipping...`);
// win64 signature
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.exe")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
// darwin url (intel)
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
updateData.platforms.darwin.url = browser_download_url;
updateData.platforms["darwin-intel"].url = browser_download_url;
updateData.platforms["darwin-x86_64"].url = browser_download_url;
}
// darwin signature (intel)
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.darwin.signature = sig;
updateData.platforms["darwin-intel"].signature = sig;
updateData.platforms["darwin-x86_64"].signature = sig;
}
// 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;
updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig;
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;
}
});
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];
}
});
// 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://download.clashverge.dev/" + value.url;
} else {
console.error(
`Failed to get release for tag: ${tag.name}`,
error.message,
);
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// 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,
);
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
}
}
// 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,
);
}
}

View File

@@ -1 +0,0 @@
avoid-breaking-exported-api = true

2152
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "2.3.1"
version = "2.1.2"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@@ -13,95 +13,80 @@ build = "build.rs"
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "2.2.0", features = [] }
tauri-build = { version = "2.0.6", features = [] }
[dependencies]
warp = "0.3.7"
anyhow = "1.0.98"
warp = "0.3"
anyhow = "1.0.97"
dirs = "6.0"
open = "5.3.2"
log = "0.4.27"
dunce = "1.0.5"
log4rs = "1.3.0"
open = "5.1"
log = "0.4"
dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4.41"
sysinfo = "0.35.2"
chrono = "0.4.40"
sysinfo = "0.33.1"
boa_engine = "0.20.0"
serde_json = "1.0.140"
serde_yaml = "0.9.34-deprecated"
once_cell = "1.21.3"
lazy_static = "1.5.0"
serde_json = "1.0"
serde_yaml = "0.9"
once_cell = "1.20.3"
port_scanner = "0.1.5"
delay_timer = "0.11.6"
parking_lot = "0.12.4"
parking_lot = "0.12"
percent-encoding = "2.3.1"
tokio = { version = "1.45.1", features = [
"rt-multi-thread",
"macros",
"time",
"sync",
] }
serde = { version = "1.0.219", features = ["derive"] }
reqwest = { version = "0.12.20", features = ["json", "rustls-tls", "cookies"] }
regex = "1.11.1"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
image = "0.25.6"
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/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
image = "0.25.5"
imageproc = "0.25.0"
tauri = { version = "2.5.1", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
rusttype = "0.9"
tauri = { version = "2.3.1", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
network-interface = { version = "2.0.1", features = ["serde"] }
tauri-plugin-shell = "2.2.2"
tauri-plugin-dialog = "2.2.2"
tauri-plugin-fs = "2.3.0"
tauri-plugin-process = "2.2.2"
tauri-plugin-clipboard-manager = "2.2.3"
tauri-plugin-deep-link = "2.3.0"
tauri-plugin-devtools = "2.0.0"
tauri-plugin-window-state = "2.2.3"
zip = "4.1.0"
reqwest_dav = "0.2.1"
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.3"
tokio-tungstenite = "0.27.0"
futures = "0.3.31"
sys-locale = "0.3.2"
async-trait = "0.1.88"
mihomo_api = { path = "src_crates/crate_mihomo_api" }
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.27.0"
libc = "0.2.173"
gethostname = "1.0.2"
hmac = "0.12.1"
sha2 = "0.10.9"
hex = "0.4.3"
tungstenite = "0.26.2"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
deelevate = "0.2.0"
winreg = "0.55.0"
winapi = { version = "0.3.9", features = [
"winbase",
"fileapi",
"winnt",
"handleapi",
"errhandlingapi",
"minwindef",
"winerror",
] }
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.4.0"
tauri-plugin-global-shortcut = "2.2.1"
tauri-plugin-updater = "2.8.1"
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]
default = ["custom-protocol"]
@@ -117,31 +102,34 @@ strip = true
[profile.dev]
incremental = true
codegen-units = 256 # 增加编译单元,提升编译速度
opt-level = 0 # 禁用优化,进一步提升编译速度
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[profile.fast-release]
inherits = "release" # 继承 release 的配置
panic = "abort" # 与 release 相同
codegen-units = 256 # 增加编译单元,提升编译速度
lto = false # 禁用 LTO提升编译速度
opt-level = 0 # 禁用优化,大幅提升编译速度
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
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]
tempfile = "3.20.0"
env_logger = "0.11.0"
mockito = "1.7.0"
tempfile = "3.17.1"
[workspace]
members = ["src_crates/crate_mihomo_api"]
# [patch.crates-io]
# bitflags = { git = "https://github.com/bitflags/bitflags", rev = "2.9.0" }
# zerocopy = { git = "https://github.com/google/zerocopy", rev = "v0.8.24" }
# tungstenite = { git = "https://github.com/snapview/tungstenite-rs", rev = "v0.26.2" }
members = [
"src/crate_mihomo_api"
]

View File

@@ -1,9 +0,0 @@
{
"identifier": "desktop-windows-capability",
"description": "permissions for desktop windows applications",
"windows": ["main"],
"permissions": [
"core:webview:allow-create-webview",
"core:webview:allow-create-webview-window"
]
}

View File

@@ -14,9 +14,8 @@
"updater:allow-download-and-install",
"process:allow-restart",
"deep-link:default",
"autostart:allow-enable",
"autostart:allow-disable",
"autostart:allow-is-enabled",
"core:window:allow-set-theme"
"window-state:default",
"window-state:default",
"autostart:default"
]
}

View File

@@ -68,6 +68,7 @@
"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",
@@ -78,6 +79,7 @@
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"shell:default",
"dialog:default"
"dialog:default",
"notification:default"
]
}

View File

@@ -916,10 +916,6 @@ FunctionEnd
!macroend
Section Uninstall
;删除 window-state.json 文件
SetShellVarContext current
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
!insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses
!insertmacro RemoveVergeService
@@ -979,23 +975,16 @@ Section Uninstall
RMDir "$INSTDIR"
!insertmacro DeleteAppUserModelId
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
!insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk"
; 兼容旧名称快捷方式
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\clash-verge.lnk"
!insertmacro UnpinShortcut "$DESKTOP\clash-verge.lnk"
!insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
!insertmacro UnpinShortcut "$DESKTOP\${MAINBINARYNAME}.lnk"
; Remove start menu shortcut
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
; 兼容旧名称快捷方式
Delete "$SMPROGRAMS\$AppStartMenuFolder\clash-verge.lnk"
Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
; Remove desktop shortcuts
Delete "$DESKTOP\${PRODUCTNAME}.lnk"
; 兼容旧名称快捷方式
Delete "$DESKTOP\clash-verge.lnk"
Delete "$DESKTOP\${MAINBINARYNAME}.lnk"
; Remove registry information for add/remove programs
!if "${INSTALLMODE}" == "both"
@@ -1015,10 +1004,6 @@ Section Uninstall
RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
${EndIf}
;删除 window-state.json 文件
SetShellVarContext current
Delete "$APPDATA\io.github.clash-verge-rev.clash-verge-rev\window-state.json"
${GetOptions} $CMDLINE "/P" $R0
IfErrors +2 0
SetAutoClose true
@@ -1061,12 +1046,12 @@ FunctionEnd
!macroend
Function CreateDesktopShortcut
CreateShortcut "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
!insertmacro SetLnkAppUserModelId "$DESKTOP\${PRODUCTNAME}.lnk"
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
!insertmacro SetLnkAppUserModelId "$DESKTOP\${MAINBINARYNAME}.lnk"
FunctionEnd
Function CreateStartMenuShortcut
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk"
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
FunctionEnd

View File

@@ -11,3 +11,4 @@ merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
imports_granularity = "Crate"

View File

@@ -1,9 +1,5 @@
use super::CmdResult;
use crate::{
feat, logging,
utils::{dirs, logging::Type},
wrap_err,
};
use crate::{feat, utils::dirs, wrap_err};
use tauri::Manager;
/// 打开应用程序所在目录
@@ -49,7 +45,7 @@ pub fn open_devtools(app_handle: tauri::AppHandle) {
/// 退出应用
#[tauri::command]
pub fn exit_app() {
feat::quit();
feat::quit(Some(0));
}
/// 重启应用
@@ -74,46 +70,46 @@ pub fn get_app_dir() -> CmdResult<String> {
Ok(app_home_dir)
}
/// 获取当前自启动状态
#[tauri::command]
pub fn get_auto_launch_status() -> CmdResult<bool> {
use crate::core::sysopt::Sysopt;
wrap_err!(Sysopt::global().get_launch_status())
}
/// 下载图标缓存
#[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()
// 检查内容类型是否为图片
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)?;
let is_html = content.len() > 15
&& (content.starts_with(b"<!DOCTYPE html")
|| content.starts_with(b"<html")
|| content.starts_with(b"<?xml"));
// 检查内容是否为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) {
@@ -126,13 +122,14 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
}
}
};
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {}
Ok(_) => {},
Err(_) => {
let _ = std::fs::remove_file(&temp_path);
if icon_path.exists() {
@@ -143,11 +140,11 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<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))
Err(format!("下载的内容不是有效图片: {}", url).into())
}
}
@@ -161,7 +158,8 @@ pub struct IconInfo {
/// 复制图标文件
#[tauri::command]
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::{fs, path::Path};
use std::fs;
use std::path::Path;
let file_path = Path::new(&path);
@@ -189,14 +187,7 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
)
.unwrap_or_default();
}
logging!(
info,
Type::Cmd,
true,
"Copying icon file path: {:?} -> file dist: {:?}",
path,
dest_path
);
match fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),
@@ -205,42 +196,3 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
Err("file not found".to_string())
}
}
/// 通知UI已准备就绪
#[tauri::command]
pub fn notify_ui_ready() -> CmdResult<()> {
log::info!(target: "app", "前端UI已准备就绪");
crate::utils::resolve::mark_ui_ready();
Ok(())
}
/// UI加载阶段
#[tauri::command]
pub fn update_ui_stage(stage: String) -> CmdResult<()> {
log::info!(target: "app", "UI加载阶段更新: {}", stage);
use crate::utils::resolve::UiReadyStage;
let stage_enum = match stage.as_str() {
"NotStarted" => UiReadyStage::NotStarted,
"Loading" => UiReadyStage::Loading,
"DomReady" => UiReadyStage::DomReady,
"ResourcesLoaded" => UiReadyStage::ResourcesLoaded,
"Ready" => UiReadyStage::Ready,
_ => {
log::warn!(target: "app", "未知的UI加载阶段: {}", stage);
return Err(format!("未知的UI加载阶段: {}", stage));
}
};
crate::utils::resolve::update_ui_ready_stage(stage_enum);
Ok(())
}
/// 重置UI就绪状态
#[tauri::command]
pub fn reset_ui_ready_state() -> CmdResult<()> {
log::info!(target: "app", "重置UI就绪状态");
crate::utils::resolve::reset_ui_ready();
Ok(())
}

View File

@@ -1,7 +1,5 @@
use super::CmdResult;
use crate::{
config::*, core::*, feat, module::mihomo::MihomoManager, process::AsyncHandler, wrap_err,
};
use crate::{config::*, core::*, feat, module::mihomo::MihomoManager, wrap_err};
use serde_yaml::Mapping;
/// 复制Clash环境变量
@@ -26,8 +24,7 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
/// 修改Clash模式
#[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult {
feat::change_clash_mode(payload);
Ok(())
Ok(feat::change_clash_mode(payload))
}
/// 切换Clash核心
@@ -40,21 +37,10 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
.await
{
Ok(_) => {
// 切换内核后重启内核
match CoreManager::global().restart_core().await {
Ok(_) => {
log::info!(target: "app", "core changed and restarted to {clash_core}");
handle::Handle::notice_message("config_core::change_success", &clash_core);
handle::Handle::refresh_clash();
Ok(None)
}
Err(err) => {
let error_msg = format!("Core changed but failed to restart: {}", err);
log::error!(target: "app", "{}", error_msg);
handle::Handle::notice_message("config_core::change_error", &error_msg);
Ok(Some(error_msg))
}
}
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();
@@ -65,18 +51,6 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>>
}
}
/// 启动核心
#[tauri::command]
pub async fn start_core() -> CmdResult {
wrap_err!(CoreManager::global().start_core().await)
}
/// 关闭核心
#[tauri::command]
pub async fn stop_core() -> CmdResult {
wrap_err!(CoreManager::global().stop_core().await)
}
/// 重启核心
#[tauri::command]
pub async fn restart_core() -> CmdResult {
@@ -124,14 +98,13 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
/// 应用或撤销DNS配置
#[tauri::command]
pub fn apply_dns_config(apply: bool) -> CmdResult {
use crate::{
config::Config,
core::{handle, CoreManager},
utils::dirs,
};
use crate::config::Config;
use crate::core::{handle, CoreManager};
use crate::utils::dirs;
use tauri::async_runtime;
// 使用spawn来处理异步操作
AsyncHandler::spawn(move || async move {
async_runtime::spawn(async move {
if apply {
// 读取DNS配置文件
let dns_path = match dirs::app_home_dir() {
@@ -245,25 +218,3 @@ pub async fn get_dns_config_content() -> CmdResult<String> {
let content = fs::read_to_string(&dns_path).map_err(|e| e.to_string())?;
Ok(content)
}
/// 验证DNS配置文件
#[tauri::command]
pub async fn validate_dns_config() -> CmdResult<(bool, String)> {
use crate::{core::CoreManager, utils::dirs};
let app_dir = dirs::app_home_dir().map_err(|e| e.to_string())?;
let dns_path = app_dir.join("dns_config.yaml");
let dns_path_str = dns_path.to_str().unwrap_or_default();
if !dns_path.exists() {
return Ok((false, "DNS config file not found".to_string()));
}
match CoreManager::global()
.validate_config_file(dns_path_str, None)
.await
{
Ok(result) => Ok(result),
Err(e) => Err(e.to_string()),
}
}

View File

@@ -1,15 +0,0 @@
use crate::module::lightweight;
use super::CmdResult;
#[tauri::command]
pub async fn entry_lightweight_mode() -> CmdResult {
lightweight::entry_lightweight_mode();
Ok(())
}
#[tauri::command]
pub async fn exit_lightweight_mode() -> CmdResult {
lightweight::exit_lightweight_mode();
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,35 +4,29 @@ use anyhow::Result;
pub type CmdResult<T = ()> = Result<T, String>;
// Command modules
pub mod app;
pub mod clash;
pub mod lightweight;
pub mod media_unlock_checker;
pub mod network;
pub mod profile;
pub mod proxy;
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 service;
pub mod system;
pub mod uwp;
pub mod validate;
pub mod verge;
pub mod webdav;
pub mod proxy;
// Re-export all command functions for backwards compatibility
pub use app::*;
pub use clash::*;
pub use lightweight::*;
pub use media_unlock_checker::*;
pub use network::*;
pub use profile::*;
pub use proxy::*;
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 service::*;
pub use system::*;
pub use uwp::*;
pub use validate::*;
pub use verge::*;
pub use webdav::*;
pub use proxy::*;

View File

@@ -1,18 +1,13 @@
use super::CmdResult;
use crate::wrap_err;
use network_interface::NetworkInterface;
use serde_yaml::Mapping;
use super::CmdResult;
use sysproxy::{Autoproxy, Sysproxy};
use tokio::task::spawn_blocking;
use serde_yaml::Mapping;
use network_interface::NetworkInterface;
/// get the system proxy
#[tauri::command]
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
let current = spawn_blocking(Sysproxy::get_system_proxy)
.await
.map_err(|e| format!("Failed to spawn blocking task for sysproxy: {}", e))?
.map_err(|e| format!("Failed to get system proxy: {}", e))?;
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(
@@ -26,11 +21,8 @@ pub async fn get_sys_proxy() -> CmdResult<Mapping> {
/// get the system proxy
#[tauri::command]
pub async fn get_auto_proxy() -> CmdResult<Mapping> {
let current = spawn_blocking(Autoproxy::get_auto_proxy)
.await
.map_err(|e| format!("Failed to spawn blocking task for autoproxy: {}", e))?
.map_err(|e| format!("Failed to get auto proxy: {}", e))?;
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());
@@ -39,25 +31,6 @@ pub async fn get_auto_proxy() -> CmdResult<Mapping> {
Ok(map)
}
/// 获取系统主机名
#[tauri::command]
pub fn get_system_hostname() -> CmdResult<String> {
use gethostname::gethostname;
// 获取系统主机名处理可能的非UTF-8字符
let hostname = match gethostname().into_string() {
Ok(name) => name,
Err(os_string) => {
// 对于包含非UTF-8的主机名使用调试格式化
let fallback = format!("{:?}", os_string);
// 去掉可能存在的引号
fallback.trim_matches('"').to_string()
}
};
Ok(hostname)
}
/// 获取网络接口列表
#[tauri::command]
pub fn get_network_interfaces() -> Vec<String> {
@@ -73,7 +46,8 @@ pub fn get_network_interfaces() -> Vec<String> {
/// 获取网络接口详细信息
#[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
use network_interface::NetworkInterface;
use network_interface::NetworkInterfaceConfig;
let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?;

View File

@@ -1,110 +1,40 @@
use super::CmdResult;
use crate::{
config::{Config, IProfiles, PrfItem, PrfOption},
core::{handle, timer::Timer, tray::Tray, CoreManager},
feat, logging, ret_err,
utils::{dirs, help, logging::Type},
wrap_err,
config::*,
core::*,
feat,
utils::{dirs, help},
log_err, ret_err, wrap_err,
};
use std::time::Duration;
use tokio::sync::Mutex;
use super::CmdResult;
// 添加全局互斥锁防止并发配置更新
static PROFILE_UPDATE_MUTEX: Mutex<()> = Mutex::const_new(());
/// 获取配置文件避免锁竞争
/// 获取配置文件列表
#[tauri::command]
pub async fn get_profiles() -> CmdResult<IProfiles> {
// 策略1: 尝试快速获取latest数据
let latest_result = tokio::time::timeout(
Duration::from_millis(500),
tokio::task::spawn_blocking(move || {
let profiles = Config::profiles();
let latest = profiles.latest();
IProfiles {
current: latest.current.clone(),
items: latest.items.clone(),
}
}),
)
.await;
match latest_result {
Ok(Ok(profiles)) => {
logging!(info, Type::Cmd, false, "快速获取配置列表成功");
return Ok(profiles);
}
Ok(Err(join_err)) => {
logging!(warn, Type::Cmd, true, "快速获取配置任务失败: {}", join_err);
}
Err(_) => {
logging!(warn, Type::Cmd, true, "快速获取配置超时(500ms)");
}
}
// 策略2: 如果快速获取失败尝试获取data()
let data_result = tokio::time::timeout(
Duration::from_secs(2),
tokio::task::spawn_blocking(move || {
let profiles = Config::profiles();
let data = profiles.data();
IProfiles {
current: data.current.clone(),
items: data.items.clone(),
}
}),
)
.await;
match data_result {
Ok(Ok(profiles)) => {
logging!(info, Type::Cmd, false, "获取draft配置列表成功");
return Ok(profiles);
}
Ok(Err(join_err)) => {
logging!(
error,
Type::Cmd,
true,
"获取draft配置任务失败: {}",
join_err
);
}
Err(_) => {
logging!(error, Type::Cmd, true, "获取draft配置超时(2秒)");
}
}
// 策略3: fallback尝试重新创建配置
logging!(
warn,
Type::Cmd,
true,
"所有获取配置策略都失败尝试fallback"
);
match tokio::task::spawn_blocking(IProfiles::new).await {
Ok(profiles) => {
logging!(info, Type::Cmd, true, "使用fallback配置成功");
Ok(profiles)
}
Err(err) => {
logging!(error, Type::Cmd, true, "fallback配置也失败: {}", err);
// 返回空配置避免崩溃
Ok(IProfiles {
current: None,
items: Some(vec![]),
})
}
}
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 {
wrap_err!(feat::enhance_profiles().await)?;
handle::Handle::refresh_clash();
Ok(())
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(())
}
}
}
/// 导入配置文件
@@ -130,17 +60,13 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
/// 更新配置文件
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option, Some(true)).await)
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) })?;
// 删除后自动清理冗余文件
let _ = Config::profiles().latest().auto_cleanup();
if should_update {
wrap_err!(CoreManager::global().update_config().await)?;
handle::Handle::refresh_clash();
@@ -150,188 +76,36 @@ pub async fn delete_profile(index: String) -> CmdResult {
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
// 获取互斥锁,防止并发执行
let _guard = PROFILE_UPDATE_MUTEX.lock().await;
logging!(info, Type::Cmd, true, "开始修改配置文件");
pub async fn patch_profiles_config(
profiles: IProfiles
) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().latest().current.clone();
logging!(info, Type::Cmd, true, "当前配置: {:?}", current_profile);
// 如果要切换配置,先检查目标配置文件是否有语法错误
if let Some(new_profile) = profiles.current.as_ref() {
if current_profile.as_ref() != Some(new_profile) {
logging!(info, Type::Cmd, true, "正在切换到新配置: {}", new_profile);
// 获取目标配置文件路径
let config_file_result = {
let profiles_config = Config::profiles();
let profiles_data = profiles_config.latest();
match profiles_data.get_item(new_profile) {
Ok(item) => {
if let Some(file) = &item.file {
let path = dirs::app_profiles_dir().map(|dir| dir.join(file));
path.ok()
} else {
None
}
}
Err(e) => {
logging!(error, Type::Cmd, true, "获取目标配置信息失败: {}", e);
None
}
}
};
// 如果获取到文件路径检查YAML语法
if let Some(file_path) = config_file_result {
if !file_path.exists() {
logging!(
error,
Type::Cmd,
true,
"目标配置文件不存在: {}",
file_path.display()
);
handle::Handle::notice_message(
"config_validate::file_not_found",
format!("{}", file_path.display()),
);
return Ok(false);
}
// 超时保护
let file_read_result = tokio::time::timeout(
Duration::from_secs(5),
tokio::fs::read_to_string(&file_path),
)
.await;
match file_read_result {
Ok(Ok(content)) => {
let yaml_parse_result = tokio::task::spawn_blocking(move || {
serde_yaml::from_str::<serde_yaml::Value>(&content)
})
.await;
match yaml_parse_result {
Ok(Ok(_)) => {
logging!(info, Type::Cmd, true, "目标配置文件语法正确");
}
Ok(Err(err)) => {
let error_msg = format!(" {}", err);
logging!(
error,
Type::Cmd,
true,
"目标配置文件存在YAML语法错误:{}",
error_msg
);
handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
&error_msg,
);
return Ok(false);
}
Err(join_err) => {
let error_msg = format!("YAML解析任务失败: {}", join_err);
logging!(error, Type::Cmd, true, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::yaml_parse_error",
&error_msg,
);
return Ok(false);
}
}
}
Ok(Err(err)) => {
let error_msg = format!("无法读取目标配置文件: {}", err);
logging!(error, Type::Cmd, true, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_error",
&error_msg,
);
return Ok(false);
}
Err(_) => {
let error_msg = "读取配置文件超时(5秒)".to_string();
logging!(error, Type::Cmd, true, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_timeout",
&error_msg,
);
return Ok(false);
}
}
}
}
}
println!("[cmd配置patch] 当前配置: {:?}", current_profile);
// 更新profiles配置
logging!(info, Type::Cmd, true, "正在更新配置草稿");
let current_value = profiles.current.clone();
let _ = Config::profiles().draft().patch_config(profiles);
// 为配置更新添加超时保护
let update_result = tokio::time::timeout(
Duration::from_secs(30), // 30秒超时
CoreManager::global().update_config(),
)
.await;
println!("[cmd配置patch] 正在更新配置草稿");
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
// 更新配置并进行验证
match update_result {
Ok(Ok((true, _))) => {
logging!(info, Type::Cmd, true, "配置更新成功");
Config::profiles().apply();
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[cmd配置patch] 配置更新成功");
handle::Handle::refresh_clash();
// 强制刷新代理缓存确保profile切换后立即获取最新节点数据
crate::process::AsyncHandler::spawn(|| async move {
if let Err(e) = super::proxy::force_refresh_proxies().await {
log::warn!(target: "app", "强制刷新代理缓存失败: {}", e);
}
});
crate::process::AsyncHandler::spawn(|| async move {
if let Err(e) = Tray::global().update_tooltip() {
log::warn!(target: "app", "异步更新托盘提示失败: {}", e);
}
if let Err(e) = Tray::global().update_menu() {
log::warn!(target: "app", "异步更新托盘菜单失败: {}", e);
}
// 保存配置文件
if let Err(e) = Config::profiles().data().save_file() {
log::warn!(target: "app", "异步保存配置文件失败: {}", e);
}
});
// 立即通知前端配置变更
if let Some(current) = &current_value {
logging!(info, Type::Cmd, true, "向前端发送配置变更事件: {}", current);
handle::Handle::notify_profile_changed(current.clone());
}
let _ = tray::Tray::global().update_tooltip();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(true)
}
Ok(Ok((false, error_msg))) => {
logging!(warn, Type::Cmd, true, "配置验证失败: {}", error_msg);
Ok((false, error_msg)) => {
println!("[cmd配置patch] 配置验证失败: {}", error_msg);
Config::profiles().discard();
// 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile {
logging!(
info,
Type::Cmd,
true,
"尝试恢复到之前的配置: {}",
prev_profile
);
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
@@ -339,49 +113,18 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
// 静默恢复,不触发验证
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
Config::profiles().apply();
crate::process::AsyncHandler::spawn(|| async move {
if let Err(e) = Config::profiles().data().save_file() {
log::warn!(target: "app", "异步保存恢复配置文件失败: {}", e);
}
});
logging!(info, Type::Cmd, true, "成功恢复到之前的配置");
wrap_err!(Config::profiles().data().save_file())?;
println!("[cmd配置patch] 成功恢复到之前的配置");
}
// 发送验证错误通知
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(false)
}
Ok(Err(e)) => {
logging!(warn, Type::Cmd, true, "更新过程发生错误: {}", e);
Err(e) => {
println!("[cmd配置patch] 更新过程发生错误: {}", e);
Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
Ok(false)
}
Err(_) => {
// 超时处理
let timeout_msg = "配置更新超时(30秒),可能是配置验证或核心通信阻塞";
logging!(error, Type::Cmd, true, "{}", timeout_msg);
Config::profiles().discard();
if let Some(prev_profile) = current_profile {
logging!(
info,
Type::Cmd,
true,
"超时后尝试恢复到之前的配置: {}",
prev_profile
);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
};
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
Config::profiles().apply();
}
handle::Handle::notice_message("config_validate::timeout", timeout_msg);
handle::Handle::notice_message("config_validate::boot_error", &e.to_string());
Ok(false)
}
}
@@ -391,47 +134,16 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
#[tauri::command]
pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String,
profile_index: String
) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
let profiles = IProfiles {
current: Some(profile_index),
items: None,
};
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 {
// 保存修改前检查是否有更新 update_interval
let update_interval_changed =
if let Ok(old_profile) = Config::profiles().latest().get_item(&index) {
let old_interval = old_profile.option.as_ref().and_then(|o| o.update_interval);
let new_interval = profile.option.as_ref().and_then(|o| o.update_interval);
old_interval != new_interval
} else {
false
};
// 保存修改
wrap_err!(Config::profiles().data().patch_item(index.clone(), profile))?;
// 如果更新间隔变更,异步刷新定时器
if update_interval_changed {
let index_clone = index.clone();
crate::process::AsyncHandler::spawn(move || async move {
logging!(info, Type::Timer, "定时器更新间隔已变更,正在刷新定时器...");
if let Err(e) = crate::core::Timer::global().refresh() {
logging!(error, Type::Timer, "刷新定时器失败: {}", e);
} else {
// 刷新成功后发送自定义事件,不触发配置重载
crate::core::handle::Handle::notify_timer_updated(index_clone);
}
});
}
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
Ok(())
}
@@ -462,11 +174,3 @@ pub fn read_profile_file(index: String) -> CmdResult<String> {
let data = wrap_err!(item.read_file())?;
Ok(data)
}
/// 获取下一次更新时间
#[tauri::command]
pub fn get_next_update_time(uid: String) -> CmdResult<Option<i64>> {
let timer = Timer::global();
let next_time = timer.get_next_update_time(&uid);
Ok(next_time)
}

View File

@@ -1,99 +1,16 @@
use super::CmdResult;
use crate::{core::handle, module::mihomo::MihomoManager, state::proxy::CmdProxyState};
use std::{
sync::Mutex,
time::{Duration, Instant},
};
use tauri::Manager;
const PROVIDERS_REFRESH_INTERVAL: Duration = Duration::from_secs(3);
const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(1);
use crate::module::mihomo::MihomoManager;
#[tauri::command]
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
let manager = MihomoManager::global();
let app_handle = handle::Handle::global().app_handle().unwrap();
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
let should_refresh = {
let mut state = cmd_proxy_state.lock().unwrap();
let now = Instant::now();
if now.duration_since(state.last_refresh_time) > PROXIES_REFRESH_INTERVAL {
state.need_refresh = true;
state.last_refresh_time = now;
}
state.need_refresh
};
if should_refresh {
let proxies = manager.get_refresh_proxies().await?;
{
let mut state = cmd_proxy_state.lock().unwrap();
state.proxies = Box::new(proxies);
state.need_refresh = false;
}
log::debug!(target: "app", "proxies刷新成功");
}
let proxies = {
let state = cmd_proxy_state.lock().unwrap();
state.proxies.clone()
};
Ok(*proxies)
}
/// 强制刷新代理缓存用于profile切换
#[tauri::command]
pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
let manager = MihomoManager::global();
let app_handle = handle::Handle::global().app_handle().unwrap();
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
log::debug!(target: "app", "强制刷新代理缓存");
let proxies = manager.get_refresh_proxies().await?;
{
let mut state = cmd_proxy_state.lock().unwrap();
state.proxies = Box::new(proxies.clone());
state.need_refresh = false;
state.last_refresh_time = Instant::now();
}
log::debug!(target: "app", "强制刷新代理缓存完成");
Ok(proxies)
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 app_handle = handle::Handle::global().app_handle().unwrap();
let cmd_proxy_state = app_handle.state::<Mutex<CmdProxyState>>();
let should_refresh = {
let mut state = cmd_proxy_state.lock().unwrap();
let now = Instant::now();
if now.duration_since(state.last_refresh_time) > PROVIDERS_REFRESH_INTERVAL {
state.need_refresh = true;
state.last_refresh_time = now;
}
state.need_refresh
};
if should_refresh {
let manager = MihomoManager::global();
let providers = manager.get_providers_proxies().await?;
{
let mut state = cmd_proxy_state.lock().unwrap();
state.providers_proxies = Box::new(providers);
state.need_refresh = false;
}
log::debug!(target: "app", "providers_proxies刷新成功");
}
let providers_proxies = {
let state = cmd_proxy_state.lock().unwrap();
state.providers_proxies.clone()
};
Ok(*providers_proxies)
let mannager = MihomoManager::global();
mannager.refresh_providers_proxies().await.unwrap();
Ok(mannager.get_providers_proxies())
}

View File

@@ -1,8 +1,11 @@
use crate::{
config::*,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, wrap_err};
use anyhow::Context;
use serde_yaml::Mapping;
use std::collections::HashMap;
use serde_yaml::Mapping;
/// 获取运行时配置
#[tauri::command]

View File

@@ -1,11 +1,10 @@
use super::CmdResult;
use crate::{
config::*,
core::*,
logging,
utils::{dirs, logging::Type},
utils::dirs,
wrap_err,
};
use super::CmdResult;
use std::fs;
/// 保存profiles的配置
@@ -21,7 +20,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件
let is_merge = item.itype.as_ref().is_some_and(|t| t == "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())?;
@@ -30,56 +29,25 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
// 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string();
logging!(
info,
Type::Config,
true,
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
file_path_str,
is_merge_file
);
println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file {
logging!(
info,
Type::Config,
true,
"[cmd配置save] 检测到merge文件只进行语法验证"
);
match CoreManager::global()
.validate_config_file(&file_path_str, Some(true))
.await
{
println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await {
Ok((true, _)) => {
logging!(
info,
Type::Config,
true,
"[cmd配置save] merge文件语法验证通过"
);
println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置
if let Err(e) = CoreManager::global().update_config().await {
logging!(
warn,
Type::Config,
true,
"[cmd配置save] 更新整体配置时发生错误: {}",
e
);
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
}
return Ok(());
}
Ok((false, error_msg)) => {
logging!(
warn,
Type::Config,
true,
"[cmd配置save] merge文件语法验证失败: {}",
error_msg
);
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 发送合并文件专用错误通知
@@ -88,75 +56,53 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
return Ok(());
}
Err(e) => {
logging!(
error,
Type::Config,
true,
"[cmd配置save] 验证过程发生错误: {}",
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
{
match CoreManager::global().validate_config_file(&file_path_str, None).await {
Ok((true, _)) => {
logging!(info, Type::Config, true, "[cmd配置save] 验证成功");
println!("[cmd配置save] 验证成功");
Ok(())
}
Ok((false, error_msg)) => {
logging!(
warn,
Type::Config,
true,
"[cmd配置save] 验证失败: {}",
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)
{
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通知处理
log::info!(target: "app", "[cmd配置save] 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 {
// 脚本错误使用专门的通知处理
log::info!(target: "app", "[cmd配置save] 脚本文件验证失败,发送通知");
println!("[cmd配置save] 脚本文件验证失败,发送通知");
let result = (false, error_msg.clone());
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
} else {
// 普通配置错误使用一般通知
log::info!(target: "app", "[cmd配置save] 其他类型验证失败,发送一般通知");
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg);
}
Ok(())
}
Err(e) => {
logging!(
error,
Type::Config,
true,
"[cmd配置save] 验证过程发生错误: {}",
e
);
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
Err(e.to_string())

View File

@@ -1,48 +0,0 @@
use super::CmdResult;
use crate::{
core::{service, CoreManager},
utils::i18n::t,
};
async fn execute_service_operation(
service_op: impl std::future::Future<Output = Result<(), impl ToString + std::fmt::Debug>>,
op_type: &str,
) -> CmdResult {
if service_op.await.is_err() {
let emsg = format!("{} {} failed", op_type, "Service");
return Err(t(emsg.as_str()));
}
if CoreManager::global().restart_core().await.is_err() {
let emsg = format!("{} {} failed", "Restart", "Core");
return Err(t(emsg.as_str()));
}
Ok(())
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
execute_service_operation(service::install_service(), "Install").await
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult {
execute_service_operation(service::uninstall_service(), "Uninstall").await
}
#[tauri::command]
pub async fn reinstall_service() -> CmdResult {
execute_service_operation(service::reinstall_service(), "Reinstall").await
}
#[tauri::command]
pub async fn repair_service() -> CmdResult {
execute_service_operation(service::force_reinstall_service(), "Repair").await
}
#[tauri::command]
pub async fn is_service_available() -> CmdResult<bool> {
service::is_service_available()
.await
.map(|_| true)
.map_err(|e| e.to_string())
}

View File

@@ -1,94 +1,35 @@
use super::CmdResult;
use crate::{
core::{handle, CoreManager},
module::sysinfo::PlatformSpecification,
};
use once_cell::sync::Lazy;
use std::{
sync::atomic::{AtomicI64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use crate::core::handle;
use crate::module::sysinfo::PlatformSpecification;
use tauri_plugin_clipboard_manager::ClipboardExt;
// 存储应用启动时间的全局变量
static APP_START_TIME: Lazy<AtomicI64> = Lazy::new(|| {
// 获取当前系统时间,转换为毫秒级时间戳
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
AtomicI64::new(now)
});
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
let sysinfo = PlatformSpecification::new_async().await;
let sysinfo = PlatformSpecification::new();
let info = format!("{:?}", sysinfo);
let app_handle = handle::Handle::global().app_handle().unwrap();
let cliboard = app_handle.clipboard();
if cliboard.write_text(info).is_err() {
if let Err(_) = cliboard.write_text(info) {
log::error!(target: "app", "Failed to write to clipboard");
}
Ok(())
}
#[tauri::command]
pub async fn get_system_info() -> CmdResult<String> {
let sysinfo = PlatformSpecification::new_async().await;
let info = format!("{:?}", sysinfo);
Ok(info)
}
/// 获取当前内核运行模式
#[tauri::command]
pub async fn get_running_mode() -> Result<String, String> {
Ok(CoreManager::global().get_running_mode().await.to_string())
}
/// 获取应用的运行时间(毫秒)
#[tauri::command]
pub fn get_app_uptime() -> CmdResult<i64> {
let start_time = APP_START_TIME.load(Ordering::Relaxed);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
Ok(now - start_time)
}
/// 检查应用是否以管理员身份运行
#[tauri::command]
#[cfg(target_os = "windows")]
pub fn is_admin() -> CmdResult<bool> {
use deelevate::{PrivilegeLevel, Token};
let result = Token::with_current_process()
.and_then(|token| token.privilege_level())
.map(|level| level != PrivilegeLevel::NotPrivileged)
.unwrap_or(false);
Ok(result)
}
/// 非Windows平台检测是否以管理员身份运行
#[tauri::command]
#[cfg(not(target_os = "windows"))]
pub fn is_admin() -> CmdResult<bool> {
#[cfg(target_os = "macos")]
{
Ok(unsafe { libc::geteuid() } == 0)
}
#[cfg(target_os = "linux")]
{
Ok(unsafe { libc::geteuid() } == 0)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
Ok(false)
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)
}

View File

@@ -4,7 +4,8 @@ use super::CmdResult;
#[cfg(windows)]
mod platform {
use super::CmdResult;
use crate::{core::win_uwp, wrap_err};
use crate::core::win_uwp;
use crate::wrap_err;
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)

View File

@@ -1,5 +1,5 @@
use crate::core::*;
use super::CmdResult;
use crate::{core::*, logging, utils::logging::Type};
/// 发送脚本验证通知消息
#[tauri::command]
@@ -13,7 +13,7 @@ pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
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"
@@ -27,15 +27,8 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
// 如果是其他类型错误,作为一般脚本错误处理
"config_validate::script_error"
};
logging!(
warn,
Type::Config,
true,
"{} 验证失败: {}",
file_type,
error_msg
);
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}
@@ -43,25 +36,16 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
/// 验证指定脚本文件
#[tauri::command]
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
logging!(info, Type::Config, true, "验证脚本文件: {}", file_path);
match CoreManager::global()
.validate_config_file(&file_path, None)
.await
{
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) // 返回验证结果布尔值
}
Ok(result.0) // 返回验证结果布尔值
},
Err(e) => {
let error_msg = e.to_string();
logging!(
error,
Type::Config,
true,
"验证脚本文件过程发生错误: {}",
error_msg
);
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
handle::Handle::notice_message("config_validate::process_terminated", &error_msg);
Ok(false)
}
@@ -73,18 +57,11 @@ pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
logging!(
info,
Type::Config,
true,
"[通知] 处理{}验证错误: {}",
file_type,
error_msg
);
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"
@@ -116,23 +93,9 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
"config_validate::yaml_error"
}
};
logging!(
warn,
Type::Config,
true,
"{} 验证失败: {}",
file_type,
error_msg
);
logging!(
info,
Type::Config,
true,
"[通知] 发送通知: status={}, msg={}",
status,
error_msg
);
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}

View File

@@ -1,12 +1,16 @@
use crate::{
config::*,
feat,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, feat, wrap_err};
/// 获取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))
Ok(IVergeResponse::from(verge_data))
}
/// 修改Verge配置

View File

@@ -1,5 +1,10 @@
use crate::{
core,
config::*,
feat,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, core, feat, wrap_err};
use reqwest_dav::list_cmd::ListFile;
/// 保存 WebDAV 配置

View File

@@ -20,12 +20,6 @@ impl IClashTemp {
map.insert(key.clone(), template.0.get(key).unwrap().clone());
}
});
// 确保 secret 字段存在且不为空
if let Some(Value::String(s)) = map.get_mut("secret") {
if s.is_empty() {
*s = "set-your-secret".to_string();
}
}
Self(Self::guard(map))
}
Err(err) => {
@@ -38,13 +32,13 @@ impl IClashTemp {
pub fn template() -> Self {
let mut map = Mapping::new();
let mut tun = Mapping::new();
let mut cors_map = Mapping::new();
tun.insert("enable".into(), false.into());
tun.insert("stack".into(), "gvisor".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());
#[cfg(not(target_os = "windows"))]
map.insert("redir-port".into(), 7895.into());
#[cfg(target_os = "linux")]
@@ -52,27 +46,14 @@ impl IClashTemp {
map.insert("mixed-port".into(), 7897.into());
map.insert("socks-port".into(), 7898.into());
map.insert("port".into(), 7899.into());
map.insert("log-level".into(), "warning".into());
map.insert("log-level".into(), "info".into());
map.insert("allow-lan".into(), false.into());
map.insert("ipv6".into(), true.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![
"tauri://localhost",
"http://tauri.localhost",
// Only enable this in dev mode
#[cfg(feature = "verge-dev")]
"http://localhost:3000",
"https://yacd.metacubex.one",
"https://metacubex.github.io",
"https://board.zash.run.place",
]
.into(),
);
map.insert("secret".into(), "set-your-secret".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());
@@ -96,26 +77,6 @@ impl IClashTemp {
config.insert("socks-port".into(), socks_port.into());
config.insert("port".into(), port.into());
config.insert("external-controller".into(), ctrl.into());
// 强制覆盖 external-controller-cors 字段,允许本地和 tauri 前端
let mut cors_map = Mapping::new();
cors_map.insert("allow-private-network".into(), true.into());
cors_map.insert(
"allow-origins".into(),
vec![
"tauri://localhost",
"http://tauri.localhost",
// Only enable this in dev mode
#[cfg(feature = "verge-dev")]
"http://localhost:3000",
"https://yacd.metacubex.one",
"https://metacubex.github.io",
"https://board.zash.run.place",
]
.into(),
);
config.insert("external-controller-cors".into(), cors_map.into());
config
}
@@ -355,13 +316,6 @@ fn test_clash_info() {
);
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClashExternalControllerCors {
pub allow_origins: Option<Vec<String>>,
pub allow_private_network: Option<bool>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct IClash {
@@ -375,7 +329,6 @@ pub struct IClash {
pub dns: Option<IClashDNS>,
pub tun: Option<IClashTUN>,
pub interface_name: Option<String>,
pub external_controller_cors: Option<IClashExternalControllerCors>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]

View File

@@ -1,10 +1,9 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{
config::PrfItem,
enhance,
utils::{dirs, help},
core::{handle, CoreManager},
enhance, logging,
process::AsyncHandler,
utils::{dirs, help, logging::Type},
};
use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell;
@@ -15,10 +14,10 @@ pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
pub struct Config {
clash_config: Draft<Box<IClashTemp>>,
verge_config: Draft<Box<IVerge>>,
profiles_config: Draft<Box<IProfiles>>,
runtime_config: Draft<Box<IRuntime>>,
clash_config: Draft<IClashTemp>,
verge_config: Draft<IVerge>,
profiles_config: Draft<IProfiles>,
runtime_config: Draft<IRuntime>,
}
impl Config {
@@ -26,26 +25,26 @@ impl Config {
static CONFIG: OnceCell<Config> = OnceCell::new();
CONFIG.get_or_init(|| Config {
clash_config: Draft::from(Box::new(IClashTemp::new())),
verge_config: Draft::from(Box::new(IVerge::new())),
profiles_config: Draft::from(Box::new(IProfiles::new())),
runtime_config: Draft::from(Box::new(IRuntime::new())),
clash_config: Draft::from(IClashTemp::new()),
verge_config: Draft::from(IVerge::new()),
profiles_config: Draft::from(IProfiles::new()),
runtime_config: Draft::from(IRuntime::new()),
})
}
pub fn clash() -> Draft<Box<IClashTemp>> {
pub fn clash() -> Draft<IClashTemp> {
Self::global().clash_config.clone()
}
pub fn verge() -> Draft<Box<IVerge>> {
pub fn verge() -> Draft<IVerge> {
Self::global().verge_config.clone()
}
pub fn profiles() -> Draft<Box<IProfiles>> {
pub fn profiles() -> Draft<IProfiles> {
Self::global().profiles_config.clone()
}
pub fn runtime() -> Draft<Box<IRuntime>> {
pub fn runtime() -> Draft<IRuntime> {
Self::global().runtime_config.clone()
}
@@ -67,41 +66,32 @@ impl Config {
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
Self::profiles().data().append_item(script_item.clone())?;
}
// 生成运行时配置
if let Err(err) = Self::generate().await {
logging!(error, Type::Config, true, "生成运行时配置失败: {}", err);
} else {
logging!(info, Type::Config, true, "生成运行时配置成功");
}
crate::log_err!(Self::generate().await);
// 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run);
let validation_result = if config_result.is_ok() {
let validation_result = if let Ok(_) = config_result {
// 验证配置文件
logging!(info, Type::Config, true, "开始验证配置");
println!("[首次启动] 开始验证配置");
match CoreManager::global().validate_config().await {
Ok((is_valid, error_msg)) => {
if !is_valid {
logging!(
warn,
Type::Config,
true,
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
error_msg
);
println!("[首次启动] 配置验证失败,使用默认最小配置启动: {}", error_msg);
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)
.await?;
Some(("config_validate::boot_error", error_msg))
} else {
logging!(info, Type::Config, true, "配置验证成功");
println!("[首次启动] 配置验证成功");
Some(("config_validate::success", String::new()))
}
}
Err(err) => {
logging!(warn, Type::Config, true, "验证进程执行失败: {}", err);
println!("[首次启动] 验证进程执行失败: {}", err);
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.await?;
@@ -109,16 +99,19 @@ impl Config {
}
}
} else {
logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置");
println!("[首次启动] 生成配置文件失败,使用默认配置");
CoreManager::global()
.use_default_config("config_validate::error", "")
.use_default_config(
"config_validate::error",
"",
)
.await?;
Some(("config_validate::error", String::new()))
};
// 在单独的任务中发送通知
if let Some((msg_type, msg_content)) = validation_result {
AsyncHandler::spawn(move || async move {
tauri::async_runtime::spawn(async move {
sleep(Duration::from_secs(2)).await;
handle::Handle::notice_message(msg_type, &msg_content);
});
@@ -149,11 +142,11 @@ impl Config {
pub async fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance().await;
*Config::runtime().draft() = Box::new(IRuntime {
*Config::runtime().draft() = IRuntime {
config: Some(config),
exists_keys,
chain_logs: logs,
});
};
Ok(())
}
@@ -164,42 +157,3 @@ pub enum ConfigType {
Run,
Check,
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn test_prfitem_from_merge_size() {
let merge_item = PrfItem::from_merge(Some("Merge".to_string())).unwrap();
dbg!(&merge_item);
let prfitem_size = mem::size_of_val(&merge_item);
dbg!(prfitem_size);
// Boxed version
let boxed_merge_item = Box::new(merge_item);
let box_prfitem_size = mem::size_of_val(&boxed_merge_item);
dbg!(box_prfitem_size);
// The size of Box<T> is always pointer-sized (usually 8 bytes on 64-bit)
// assert_eq!(box_prfitem_size, mem::size_of::<Box<PrfItem>>());
assert!(box_prfitem_size < prfitem_size);
}
#[test]
fn test_draft_size_non_boxed() {
let draft = Draft::from(IRuntime::new());
let iruntime_size = std::mem::size_of_val(&draft);
dbg!(iruntime_size);
assert_eq!(iruntime_size, std::mem::size_of::<Draft<IRuntime>>());
}
#[test]
fn test_draft_size_boxed() {
let draft = Draft::from(Box::new(IRuntime::new()));
let box_iruntime_size = std::mem::size_of_val(&draft);
dbg!(box_iruntime_size);
assert_eq!(
box_iruntime_size,
std::mem::size_of::<Draft<Box<IRuntime>>>()
);
}
}

View File

@@ -9,21 +9,13 @@ pub struct Draft<T: Clone + ToOwned> {
macro_rules! draft_define {
($id: ident) => {
impl From<$id> for Draft<$id> {
fn from(data: $id) -> Self {
Draft {
inner: Arc::new(Mutex::new((data, None))),
}
}
}
impl Draft<Box<$id>> {
impl Draft<$id> {
#[allow(unused)]
pub fn data(&self) -> MappedMutexGuard<Box<$id>> {
pub fn data(&self) -> MappedMutexGuard<$id> {
MutexGuard::map(self.inner.lock(), |guard| &mut guard.0)
}
pub fn latest(&self) -> MappedMutexGuard<Box<$id>> {
pub fn latest(&self) -> MappedMutexGuard<$id> {
MutexGuard::map(self.inner.lock(), |inner| {
if inner.1.is_none() {
&mut inner.0
@@ -33,7 +25,7 @@ macro_rules! draft_define {
})
}
pub fn draft(&self) -> MappedMutexGuard<Box<$id>> {
pub fn draft(&self) -> MappedMutexGuard<$id> {
MutexGuard::map(self.inner.lock(), |inner| {
if inner.1.is_none() {
inner.1 = Some(inner.0.clone());
@@ -43,7 +35,7 @@ macro_rules! draft_define {
})
}
pub fn apply(&self) -> Option<Box<$id>> {
pub fn apply(&self) -> Option<$id> {
let mut inner = self.inner.lock();
match inner.1.take() {
@@ -56,14 +48,14 @@ macro_rules! draft_define {
}
}
pub fn discard(&self) -> Option<Box<$id>> {
pub fn discard(&self) -> Option<$id> {
let mut inner = self.inner.lock();
inner.1.take()
}
}
impl From<Box<$id>> for Draft<Box<$id>> {
fn from(data: Box<$id>) -> Self {
impl From<$id> for Draft<$id> {
fn from(data: $id) -> Self {
Draft {
inner: Arc::new(Mutex::new((data, None))),
}
@@ -79,12 +71,12 @@ draft_define!(IRuntime);
draft_define!(IVerge);
#[test]
fn test_draft_box() {
let verge = Box::new(IVerge {
fn test_draft() {
let verge = IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
..IVerge::default()
});
};
let draft = Draft::from(verge);
@@ -94,11 +86,10 @@ fn test_draft_box() {
assert_eq!(draft.draft().enable_auto_launch, Some(true));
assert_eq!(draft.draft().enable_tun_mode, Some(false));
{
let mut d = draft.draft();
d.enable_auto_launch = Some(false);
d.enable_tun_mode = Some(true);
}
let mut d = draft.draft();
d.enable_auto_launch = Some(false);
d.enable_tun_mode = Some(true);
drop(d);
assert_eq!(draft.data().enable_auto_launch, Some(true));
assert_eq!(draft.data().enable_tun_mode, Some(false));
@@ -118,17 +109,18 @@ fn test_draft_box() {
assert_eq!(draft.draft().enable_auto_launch, Some(false));
assert_eq!(draft.draft().enable_tun_mode, Some(true));
{
let mut d = draft.draft();
d.enable_auto_launch = Some(true);
}
let mut d = draft.draft();
d.enable_auto_launch = Some(true);
drop(d);
assert_eq!(draft.data().enable_auto_launch, Some(false));
assert_eq!(draft.draft().enable_auto_launch, Some(true));
assert!(draft.discard().is_some());
assert_eq!(draft.data().enable_auto_launch, Some(false));
assert!(draft.discard().is_none());
assert_eq!(draft.draft().enable_auto_launch, Some(false));

View File

@@ -8,9 +8,14 @@ mod profiles;
mod runtime;
mod verge;
pub use self::{
clash::*, config::*, draft::*, encrypt::*, prfitem::*, profiles::*, runtime::*, 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;";

View File

@@ -1,13 +1,10 @@
use crate::utils::{
dirs, help,
network::{NetworkManager, ProxyType},
tmpl,
};
use crate::utils::{dirs, help, resolve::VERSION, tmpl};
use anyhow::{bail, Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::{fs, time::Duration};
use std::fs;
use sysproxy::Sysproxy;
use super::Config;
@@ -92,12 +89,6 @@ pub struct PrfOption {
#[serde(skip_serializing_if = "Option::is_none")]
pub update_interval: Option<u64>,
/// for `remote` profile
/// HTTP request timeout in seconds
/// default is 60 seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_seconds: Option<u64>,
/// for `remote` profile
/// disable certificate validation
/// default is `false`
@@ -131,7 +122,6 @@ impl PrfOption {
a.rules = b.rules.or(a.rules);
a.proxies = b.proxies.or(a.proxies);
a.groups = b.groups.or(a.groups);
a.timeout_seconds = b.timeout_seconds.or(a.timeout_seconds);
Some(a)
}
t => t.0.or(t.1),
@@ -244,46 +234,65 @@ impl PrfItem {
option: Option<PrfOption>,
) -> Result<PrfItem> {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.is_some_and(|o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.is_some_and(|o| o.self_proxy.unwrap_or(false));
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.is_some_and(|o| o.danger_accept_invalid_certs.unwrap_or(false));
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 timeout = opt_ref.and_then(|o| o.timeout_seconds).unwrap_or(20);
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();
// 选择代理类型
let proxy_type = if self_proxy {
ProxyType::Localhost
} else if with_proxy {
ProxyType::System
} else {
ProxyType::None
};
// 使用软件自己的代理
if self_proxy {
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
// 使用网络管理器发送请求
let resp = match NetworkManager::global()
.get_with_interrupt(
url,
proxy_type,
Some(timeout),
user_agent.clone(),
accept_invalid_certs,
)
.await
{
Ok(r) => r,
Err(e) => {
tokio::time::sleep(Duration::from_millis(100)).await;
bail!("failed to fetch remote profile: {}", e);
let proxy_scheme = format!("http://127.0.0.1:{port}");
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
// 使用系统代理
else if with_proxy {
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
}
let version = match VERSION.get() {
Some(v) => format!("clash-verge/v{}", v),
None => "clash-verge/unknown".to_string(),
};
builder = builder.danger_accept_invalid_certs(accept_invalid_certs);
builder = builder.user_agent(user_agent.unwrap_or(version));
let resp = builder.build()?.get(url).send().await?;
let status_code = resp.status();
if !StatusCode::is_success(&status_code) {
bail!("failed to fetch remote profile with status {status_code}")

View File

@@ -3,7 +3,7 @@ use crate::utils::{dirs, help};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::{collections::HashSet, fs, io::Write};
use std::{fs, io::Write};
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -15,14 +15,6 @@ pub struct IProfiles {
pub items: Option<Vec<PrfItem>>,
}
/// 清理结果
#[derive(Debug, Clone)]
pub struct CleanupResult {
pub total_files: usize,
pub deleted_files: Vec<String>,
pub failed_deletions: Vec<String>,
}
macro_rules! patch {
($lv: expr, $rv: expr, $key: tt) => {
if ($rv.$key).is_some() {
@@ -480,210 +472,15 @@ impl IProfiles {
/// 获取所有的profiles(uid名称)
pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> {
self.items.as_ref().map(|items| {
items
.iter()
.filter_map(|e| {
if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) {
Some((uid, name))
} else {
None
}
})
.collect()
})
}
/// 以 app 中的 profile 列表为准,删除不再需要的文件
pub fn cleanup_orphaned_files(&self) -> Result<CleanupResult> {
let profiles_dir = dirs::app_profiles_dir()?;
if !profiles_dir.exists() {
return Ok(CleanupResult {
total_files: 0,
deleted_files: vec![],
failed_deletions: vec![],
});
}
// 获取所有 active profile 的文件名集合
let active_files = self.get_all_active_files();
// 添加全局扩展配置文件到保护列表
let protected_files = self.get_protected_global_files();
// 扫描 profiles 目录下的所有文件
let mut total_files = 0;
let mut deleted_files = vec![];
let mut failed_deletions = vec![];
for entry in std::fs::read_dir(&profiles_dir)? {
let entry = entry?;
let path = entry.path();
if !path.is_file() {
continue;
}
total_files += 1;
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
if Self::is_profile_file(file_name) {
// 检查是否为全局扩展文件
if protected_files.contains(file_name) {
log::debug!(target: "app", "保护全局扩展配置文件: {}", file_name);
continue;
}
// 检查是否为活跃文件
if !active_files.contains(file_name) {
match std::fs::remove_file(&path) {
Ok(_) => {
deleted_files.push(file_name.to_string());
log::info!(target: "app", "已清理冗余文件: {}", file_name);
}
Err(e) => {
failed_deletions.push(format!("{}: {}", file_name, e));
log::warn!(target: "app", "清理文件失败: {} - {}", file_name, e);
}
}
}
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
}
}
}
let result = CleanupResult {
total_files,
deleted_files,
failed_deletions,
};
log::info!(
target: "app",
"Profile 文件清理完成: 总文件数={}, 删除文件数={}, 失败数={}",
result.total_files,
result.deleted_files.len(),
result.failed_deletions.len()
);
Ok(result)
}
/// 不删除全局扩展配置
fn get_protected_global_files(&self) -> HashSet<String> {
let mut protected_files = HashSet::new();
protected_files.insert("Merge.yaml".to_string());
protected_files.insert("Script.js".to_string());
protected_files
}
/// 获取所有 active profile 关联的文件名
fn get_all_active_files(&self) -> HashSet<String> {
let mut active_files = HashSet::new();
if let Some(items) = &self.items {
for item in items {
// 收集所有类型 profile 的文件
if let Some(file) = &item.file {
active_files.insert(file.clone());
}
// 对于主 profile 类型remote/local还需要收集其关联的扩展文件
if let Some(itype) = &item.itype {
if itype == "remote" || itype == "local" {
if let Some(option) = &item.option {
// 收集关联的扩展文件
if let Some(merge_uid) = &option.merge {
if let Ok(merge_item) = self.get_item(merge_uid) {
if let Some(file) = &merge_item.file {
active_files.insert(file.clone());
}
}
}
if let Some(script_uid) = &option.script {
if let Ok(script_item) = self.get_item(script_uid) {
if let Some(file) = &script_item.file {
active_files.insert(file.clone());
}
}
}
if let Some(rules_uid) = &option.rules {
if let Ok(rules_item) = self.get_item(rules_uid) {
if let Some(file) = &rules_item.file {
active_files.insert(file.clone());
}
}
}
if let Some(proxies_uid) = &option.proxies {
if let Ok(proxies_item) = self.get_item(proxies_uid) {
if let Some(file) = &proxies_item.file {
active_files.insert(file.clone());
}
}
}
if let Some(groups_uid) = &option.groups {
if let Ok(groups_item) = self.get_item(groups_uid) {
if let Some(file) = &groups_item.file {
active_files.insert(file.clone());
}
}
}
}
}
}
}
}
active_files
}
/// 检查文件名是否符合 profile 文件的命名规则
fn is_profile_file(filename: &str) -> bool {
// 匹配各种 profile 文件格式
// R12345678.yaml (remote)
// L12345678.yaml (local)
// m12345678.yaml (merge)
// s12345678.js (script)
// r12345678.yaml (rules)
// p12345678.yaml (proxies)
// g12345678.yaml (groups)
let patterns = [
r"^[RL][a-zA-Z0-9]+\.yaml$", // Remote/Local profiles
r"^m[a-zA-Z0-9]+\.yaml$", // Merge files
r"^s[a-zA-Z0-9]+\.js$", // Script files
r"^[rpg][a-zA-Z0-9]+\.yaml$", // Rules/Proxies/Groups files
];
patterns.iter().any(|pattern| {
regex::Regex::new(pattern)
.map(|re| re.is_match(filename))
.unwrap_or(false)
})
}
pub fn auto_cleanup(&self) -> Result<()> {
match self.cleanup_orphaned_files() {
Ok(result) => {
if !result.deleted_files.is_empty() {
log::info!(
target: "app",
"自动清理完成,删除了 {} 个冗余文件",
result.deleted_files.len()
);
}
Ok(())
}
Err(e) => {
log::warn!(target: "app", "自动清理失败: {}", e);
Ok(())
}
}).collect()),
None => None,
}
}
}

View File

@@ -1,8 +1,7 @@
use crate::{
config::{deserialize_encrypted, serialize_encrypted, DEFAULT_PAC},
logging,
utils::{dirs, help, i18n, logging::Type},
};
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;
use serde::{Deserialize, Serialize};
@@ -89,9 +88,6 @@ pub struct IVerge {
/// pac script content
pub pac_file_content: Option<String>,
/// proxy host address
pub proxy_host: Option<String>,
/// theme setting
pub theme_setting: Option<IVergeTheme>,
@@ -105,14 +101,10 @@ pub struct IVerge {
/// hotkey map
/// format: {func},{key}
pub hotkeys: Option<Vec<String>>,
/// enable global hotkey
pub enable_global_hotkey: Option<bool>,
/// 首页卡片设置
/// 控制首页各个卡片的显示和隐藏
pub home_cards: Option<serde_json::Value>,
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
@@ -135,7 +127,7 @@ pub struct IVerge {
pub test_list: Option<Vec<IVergeTestItem>>,
/// 日志清理
/// 0: 不清理; 1: 1天2: 7天; 3: 30天; 4: 90天
/// 0: 不清理; 1: 7天; 2: 30天; 3: 90天
pub auto_log_clean: Option<i32>,
/// 是否启用随机端口
@@ -193,19 +185,8 @@ pub struct IVerge {
pub enable_tray_speed: Option<bool>,
pub enable_tray_icon: Option<bool>,
/// 自动进入轻量模式
pub enable_auto_light_weight_mode: Option<bool>,
/// 自动进入轻量模式的延迟(分钟)
pub auto_light_weight_minutes: Option<u64>,
/// 启用代理页面自动滚动
pub enable_hover_jump_navigator: Option<bool>,
/// 服务状态跟踪
pub service_state: Option<crate::core::service::ServiceState>,
/// 轻量模式 - 只保留内核运行
pub enable_lite_mode: Option<bool>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -233,93 +214,6 @@ pub struct IVergeTheme {
}
impl IVerge {
/// 有效的clash核心名称
pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"];
/// 验证并修正配置文件中的clash_core值
pub fn validate_and_fix_config() -> Result<()> {
let config_path = dirs::verge_path()?;
let mut config = match help::read_yaml::<IVerge>(&config_path) {
Ok(config) => config,
Err(_) => Self::template(),
};
let mut needs_fix = false;
if let Some(ref core) = config.clash_core {
let core_str = core.trim();
if core_str.is_empty() || !Self::VALID_CLASH_CORES.contains(&core_str) {
logging!(
warn,
Type::Config,
true,
"启动时发现无效的clash_core配置: '{}', 将自动修正为 'verge-mihomo'",
core
);
config.clash_core = Some("verge-mihomo".to_string());
needs_fix = true;
}
} else {
logging!(
info,
Type::Config,
true,
"启动时发现未配置clash_core, 将设置为默认值 'verge-mihomo'"
);
config.clash_core = Some("verge-mihomo".to_string());
needs_fix = true;
}
// 修正后保存配置
if needs_fix {
logging!(info, Type::Config, true, "正在保存修正后的配置文件...");
help::save_yaml(&config_path, &config, Some("# Clash Verge Config"))?;
logging!(
info,
Type::Config,
true,
"配置文件修正完成,需要重新加载配置"
);
Self::reload_config_after_fix(config)?;
} else {
logging!(
info,
Type::Config,
true,
"clash_core配置验证通过: {:?}",
config.clash_core
);
}
Ok(())
}
/// 配置修正后重新加载配置
fn reload_config_after_fix(updated_config: IVerge) -> Result<()> {
use crate::config::Config;
let config_draft = Config::verge();
*config_draft.draft() = Box::new(updated_config.clone());
config_draft.apply();
logging!(
info,
Type::Config,
true,
"内存配置已强制更新新的clash_core: {:?}",
updated_config.clash_core
);
Ok(())
}
pub fn get_valid_clash_core(&self) -> String {
self.clash_core
.clone()
.unwrap_or_else(|| "verge-mihomo".to_string())
}
fn get_system_language() -> String {
let sys_lang = sys_locale::get_locale()
.unwrap_or_else(|| String::from("en"))
@@ -354,7 +248,7 @@ impl IVerge {
env_type: Some("bash".into()),
#[cfg(target_os = "windows")]
env_type: Some("powershell".into()),
start_page: Some("/home".into()),
start_page: Some("/".into()),
traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_group_icon: Some(true),
@@ -366,11 +260,9 @@ impl IVerge {
tun_tray_icon: Some(false),
enable_auto_launch: Some(false),
enable_silent_start: Some(false),
enable_hover_jump_navigator: Some(true),
enable_system_proxy: Some(false),
proxy_auto_config: Some(false),
pac_file_content: Some(DEFAULT_PAC.into()),
proxy_host: Some("127.0.0.1".into()),
enable_random_port: Some(false),
#[cfg(not(target_os = "windows"))]
verge_redir_port: Some(7895),
@@ -395,14 +287,10 @@ impl IVerge {
webdav_url: None,
webdav_username: None,
webdav_password: None,
enable_tray_speed: Some(false),
enable_tray_icon: Some(true),
enable_tray_speed: Some(true),
enable_global_hotkey: Some(true),
enable_auto_light_weight_mode: Some(false),
auto_light_weight_minutes: Some(10),
enable_dns_settings: Some(false),
home_cards: None,
service_state: None,
enable_lite_mode: Some(false),
enable_dns_settings: Some(true),
..Self::default()
}
}
@@ -443,7 +331,6 @@ impl IVerge {
patch!(enable_tun_mode);
patch!(enable_auto_launch);
patch!(enable_silent_start);
patch!(enable_hover_jump_navigator);
patch!(enable_random_port);
#[cfg(not(target_os = "windows"))]
patch!(verge_redir_port);
@@ -465,7 +352,7 @@ impl IVerge {
patch!(proxy_guard_duration);
patch!(proxy_auto_config);
patch!(pac_file_content);
patch!(proxy_host);
patch!(theme_setting);
patch!(web_ui_list);
patch!(clash_core);
@@ -485,12 +372,8 @@ impl IVerge {
patch!(webdav_username);
patch!(webdav_password);
patch!(enable_tray_speed);
patch!(enable_tray_icon);
patch!(enable_auto_light_weight_mode);
patch!(auto_light_weight_minutes);
patch!(enable_lite_mode);
patch!(enable_dns_settings);
patch!(home_cards);
patch!(service_state);
}
/// 在初始化前尝试拿到单例端口的值
@@ -549,7 +432,6 @@ pub struct IVergeResponse {
pub proxy_guard_duration: Option<u64>,
pub proxy_auto_config: Option<bool>,
pub pac_file_content: Option<String>,
pub proxy_host: Option<String>,
pub theme_setting: Option<IVergeTheme>,
pub web_ui_list: Option<Vec<String>>,
pub clash_core: Option<String>,
@@ -580,19 +462,12 @@ pub struct IVergeResponse {
pub webdav_username: Option<String>,
pub webdav_password: Option<String>,
pub enable_tray_speed: Option<bool>,
pub enable_tray_icon: Option<bool>,
pub enable_auto_light_weight_mode: Option<bool>,
pub auto_light_weight_minutes: Option<u64>,
pub enable_lite_mode: Option<bool>,
pub enable_dns_settings: Option<bool>,
pub home_cards: Option<serde_json::Value>,
pub enable_hover_jump_navigator: Option<bool>,
pub service_state: Option<crate::core::service::ServiceState>,
}
impl From<IVerge> for IVergeResponse {
fn from(verge: IVerge) -> Self {
// 先获取验证后的clash_core值避免后续借用冲突
let valid_clash_core = verge.get_valid_clash_core();
Self {
app_log_level: verge.app_log_level,
language: verge.language,
@@ -621,10 +496,9 @@ impl From<IVerge> for IVergeResponse {
proxy_guard_duration: verge.proxy_guard_duration,
proxy_auto_config: verge.proxy_auto_config,
pac_file_content: verge.pac_file_content,
proxy_host: verge.proxy_host,
theme_setting: verge.theme_setting,
web_ui_list: verge.web_ui_list,
clash_core: Some(valid_clash_core),
clash_core: verge.clash_core,
hotkeys: verge.hotkeys,
auto_close_connection: verge.auto_close_connection,
auto_check_update: verge.auto_check_update,
@@ -652,13 +526,8 @@ impl From<IVerge> for IVergeResponse {
webdav_username: verge.webdav_username,
webdav_password: verge.webdav_password,
enable_tray_speed: verge.enable_tray_speed,
enable_tray_icon: verge.enable_tray_icon,
enable_auto_light_weight_mode: verge.enable_auto_light_weight_mode,
auto_light_weight_minutes: verge.auto_light_weight_minutes,
enable_lite_mode: verge.enable_lite_mode,
enable_dns_settings: verge.enable_dns_settings,
home_cards: verge.home_cards,
enable_hover_jump_navigator: verge.enable_hover_jump_navigator,
service_state: verge.service_state,
}
}
}

View File

@@ -1,23 +1,19 @@
use crate::{config::Config, utils::dirs};
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,
env::{consts::OS, temp_dir},
fs,
io::Write,
path::PathBuf,
sync::Arc,
time::Duration,
};
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;
// 应用版本常量,来自 tauri.conf.json
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
const TIMEOUT_UPLOAD: u64 = 300; // 上传超时 5 分钟
const TIMEOUT_DOWNLOAD: u64 = 300; // 下载超时 5 分钟
const TIMEOUT_LIST: u64 = 3; // 列表超时 30 秒
@@ -108,18 +104,6 @@ impl WebDavClient {
reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_secs(op.timeout()))
.user_agent(format!(
"clash-verge/{} ({} WebDAV-Client)",
APP_VERSION, OS
))
.redirect(reqwest::redirect::Policy::custom(|attempt| {
// 允许所有请求类型的重定向包括PUT
if attempt.previous().len() >= 5 {
attempt.error("重定向次数过多")
} else {
attempt.follow()
}
}))
.build()
.unwrap(),
)
@@ -127,13 +111,12 @@ impl WebDavClient {
.set_auth(reqwest_dav::Auth::Basic(config.username, config.password))
.build()?;
// 尝试检查目录是否存在,如果不存在尝试创建,但创建失败不报错
if client
// 确保备份目录存在
let list_result = client
.list(dirs::BACKUP_DIR, reqwest_dav::Depth::Number(0))
.await
.is_err()
{
let _ = client.mkcol(dirs::BACKUP_DIR).await;
.await;
if list_result.is_err() {
client.mkcol(dirs::BACKUP_DIR).await?;
}
// 缓存客户端
@@ -153,41 +136,9 @@ impl WebDavClient {
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 file_content = fs::read(&file_path)?;
// 添加超时保护
let upload_result = timeout(
Duration::from_secs(TIMEOUT_UPLOAD),
client.put(&webdav_path, file_content.clone()),
)
.await;
match upload_result {
Err(_) => {
log::warn!("Upload timed out, retrying once");
tokio::time::sleep(Duration::from_millis(500)).await;
timeout(
Duration::from_secs(TIMEOUT_UPLOAD),
client.put(&webdav_path, file_content),
)
.await??;
Ok(())
}
Ok(Err(e)) => {
log::warn!("Upload failed, retrying once: {}", e);
tokio::time::sleep(Duration::from_millis(500)).await;
timeout(
Duration::from_secs(TIMEOUT_UPLOAD),
client.put(&webdav_path, file_content),
)
.await??;
Ok(())
}
Ok(Ok(_)) => Ok(()),
}
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> {

File diff suppressed because it is too large Load Diff

View File

@@ -1,294 +1,31 @@
use crate::log_err;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::{
sync::{
atomic::{AtomicU64, Ordering},
mpsc, Arc,
},
thread,
time::{Duration, Instant},
};
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager, WebviewWindow};
use tauri_plugin_shell::process::CommandChild;
use crate::{logging, utils::logging::Type};
/// 不同类型的前端通知
#[derive(Debug, Clone)]
enum FrontendEvent {
RefreshClash,
RefreshVerge,
NoticeMessage { status: String, message: String },
ProfileChanged { current_profile_id: String },
TimerUpdated { profile_index: String },
StartupCompleted,
ProfileUpdateStarted { uid: String },
ProfileUpdateCompleted { uid: String },
}
/// 事件发送统计和监控
#[derive(Debug, Default)]
struct EventStats {
total_sent: AtomicU64,
total_errors: AtomicU64,
last_error_time: RwLock<Option<Instant>>,
}
/// 存储启动期间的错误消息
#[derive(Debug, Clone)]
struct ErrorMessage {
status: String,
message: String,
}
/// 全局前端通知系统
#[derive(Debug)]
struct NotificationSystem {
sender: Option<mpsc::Sender<FrontendEvent>>,
worker_handle: Option<thread::JoinHandle<()>>,
is_running: bool,
stats: EventStats,
last_emit_time: RwLock<Instant>,
/// 当通知系统失败超过阈值时,进入紧急模式
emergency_mode: RwLock<bool>,
}
impl Default for NotificationSystem {
fn default() -> Self {
Self::new()
}
}
impl NotificationSystem {
fn new() -> Self {
Self {
sender: None,
worker_handle: None,
is_running: false,
stats: EventStats::default(),
last_emit_time: RwLock::new(Instant::now()),
emergency_mode: RwLock::new(false),
}
}
/// 启动通知处理线程
fn start(&mut self) {
if self.is_running {
return;
}
let (tx, rx) = mpsc::channel();
self.sender = Some(tx);
self.is_running = true;
*self.last_emit_time.write() = Instant::now();
self.worker_handle = Some(
thread::Builder::new()
.name("frontend-notifier".into())
.spawn(move || {
let handle = Handle::global();
while !handle.is_exiting() {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(event) => {
let system_guard = handle.notification_system.read();
if system_guard.as_ref().is_none() {
log::warn!("NotificationSystem not found in handle while processing event.");
continue;
}
let system = system_guard.as_ref().unwrap();
let is_emergency = *system.emergency_mode.read();
if is_emergency {
if let FrontendEvent::NoticeMessage { ref status, .. } = event {
if status == "info" {
log::warn!(
"Emergency mode active, skipping info message"
);
continue;
}
}
}
if let Some(window) = handle.get_window() {
*system.last_emit_time.write() = Instant::now();
let (event_name_str, payload_result) = match event {
FrontendEvent::RefreshClash => {
("verge://refresh-clash-config", Ok(serde_json::json!("yes")))
}
FrontendEvent::RefreshVerge => {
("verge://refresh-verge-config", Ok(serde_json::json!("yes")))
}
FrontendEvent::NoticeMessage { status, message } => {
match serde_json::to_value((status, message)) {
Ok(p) => ("verge://notice-message", Ok(p)),
Err(e) => {
log::error!("Failed to serialize NoticeMessage payload: {}", e);
("verge://notice-message", Err(e))
}
}
}
FrontendEvent::ProfileChanged { current_profile_id } => {
("profile-changed", Ok(serde_json::json!(current_profile_id)))
}
FrontendEvent::TimerUpdated { profile_index } => {
("verge://timer-updated", Ok(serde_json::json!(profile_index)))
}
FrontendEvent::StartupCompleted => {
("verge://startup-completed", Ok(serde_json::json!(null)))
}
FrontendEvent::ProfileUpdateStarted { uid } => {
("profile-update-started", Ok(serde_json::json!({ "uid": uid })))
}
FrontendEvent::ProfileUpdateCompleted { uid } => {
("profile-update-completed", Ok(serde_json::json!({ "uid": uid })))
}
};
if let Ok(payload) = payload_result {
match window.emit(event_name_str, payload) {
Ok(_) => {
system.stats.total_sent.fetch_add(1, Ordering::SeqCst);
// 记录成功发送的事件
if log::log_enabled!(log::Level::Debug) {
log::debug!("Successfully emitted event: {}", event_name_str);
}
}
Err(e) => {
log::warn!("Failed to emit event {}: {}", event_name_str, e);
system.stats.total_errors.fetch_add(1, Ordering::SeqCst);
*system.stats.last_error_time.write() = Some(Instant::now());
let errors = system.stats.total_errors.load(Ordering::SeqCst);
const EMIT_ERROR_THRESHOLD: u64 = 10;
if errors > EMIT_ERROR_THRESHOLD && !*system.emergency_mode.read() {
log::warn!(
"Reached {} emit errors, entering emergency mode",
EMIT_ERROR_THRESHOLD
);
*system.emergency_mode.write() = true;
}
}
}
} else {
system.stats.total_errors.fetch_add(1, Ordering::SeqCst);
*system.stats.last_error_time.write() = Some(Instant::now());
log::warn!("Skipped emitting event due to payload serialization error for {}", event_name_str);
}
} else {
log::warn!("No window found, skipping event emit.");
}
thread::sleep(Duration::from_millis(20));
}
Err(mpsc::RecvTimeoutError::Timeout) => {
continue;
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
log::info!(
"Notification channel disconnected, exiting worker thread"
);
break;
}
}
}
log::info!("Notification worker thread exiting");
})
.expect("Failed to start notification worker thread"),
);
}
/// 发送事件到队列
fn send_event(&self, event: FrontendEvent) -> bool {
if *self.emergency_mode.read() {
if let FrontendEvent::NoticeMessage { ref status, .. } = event {
if status == "info" {
log::info!("Skipping info message in emergency mode");
return false;
}
}
}
if let Some(sender) = &self.sender {
match sender.send(event) {
Ok(_) => true,
Err(e) => {
log::warn!("Failed to send event to notification queue: {:?}", e);
self.stats.total_errors.fetch_add(1, Ordering::SeqCst);
*self.stats.last_error_time.write() = Some(Instant::now());
false
}
}
} else {
log::warn!("Notification system not started, can't send event");
false
}
}
fn shutdown(&mut self) {
log::info!("NotificationSystem shutdown initiated");
self.is_running = false;
// 先关闭发送端,让接收端知道不会再有新消息
if let Some(sender) = self.sender.take() {
drop(sender);
}
// 设置超时避免无限等待
if let Some(handle) = self.worker_handle.take() {
match handle.join() {
Ok(_) => {
log::info!("NotificationSystem worker thread joined successfully");
}
Err(e) => {
log::error!("NotificationSystem worker thread join failed: {:?}", e);
}
}
}
log::info!("NotificationSystem shutdown completed");
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
pub is_exiting: Arc<RwLock<bool>>,
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
startup_completed: Arc<RwLock<bool>>,
notification_system: Arc<RwLock<Option<NotificationSystem>>>,
}
impl Default for Handle {
fn default() -> Self {
Self {
app_handle: Arc::new(RwLock::new(None)),
is_exiting: Arc::new(RwLock::new(false)),
startup_errors: Arc::new(RwLock::new(Vec::new())),
startup_completed: Arc::new(RwLock::new(false)),
notification_system: Arc::new(RwLock::new(Some(NotificationSystem::new()))),
}
}
pub core_process: Arc<RwLock<Option<CommandChild>>>,
}
impl Handle {
pub fn global() -> &'static Handle {
static HANDLE: OnceCell<Handle> = OnceCell::new();
HANDLE.get_or_init(Handle::default)
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(RwLock::new(None)),
is_exiting: Arc::new(RwLock::new(false)),
core_process: Arc::new(RwLock::new(None)),
})
}
pub fn init(&self, app_handle: &AppHandle) {
{
let mut handle = self.app_handle.write();
*handle = Some(app_handle.clone());
}
let mut system_opt = self.notification_system.write();
if let Some(system) = system_opt.as_mut() {
system.start();
}
let mut handle = self.app_handle.write();
*handle = Some(app_handle.clone());
}
pub fn app_handle(&self) -> Option<AppHandle> {
@@ -296,7 +33,7 @@ impl Handle {
}
pub fn get_window(&self) -> Option<WebviewWindow> {
let app_handle = self.app_handle()?;
let app_handle = self.app_handle().unwrap();
let window: Option<WebviewWindow> = app_handle.get_webview_window("main");
if window.is_none() {
log::debug!(target:"app", "main window not found");
@@ -305,213 +42,48 @@ impl Handle {
}
pub fn refresh_clash() {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::RefreshClash);
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-clash-config", "yes"));
}
}
pub fn refresh_verge() {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::RefreshVerge);
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-verge-config", "yes"));
}
}
pub fn notify_profile_changed(profile_id: String) {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::ProfileChanged {
current_profile_id: profile_id,
});
} else {
log::warn!(
"Notification system not initialized when trying to send ProfileChanged event."
);
#[allow(unused)]
pub fn refresh_profiles() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-profiles-config", "yes"));
}
}
pub fn notify_timer_updated(profile_index: String) {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::TimerUpdated { profile_index });
} else {
log::warn!(
"Notification system not initialized when trying to send TimerUpdated event."
);
}
}
pub fn notify_startup_completed() {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::StartupCompleted);
} else {
log::warn!(
"Notification system not initialized when trying to send StartupCompleted event."
);
}
}
pub fn notify_profile_update_started(uid: String) {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::ProfileUpdateStarted { uid });
} else {
log::warn!("Notification system not initialized when trying to send ProfileUpdateStarted event.");
}
}
pub fn notify_profile_update_completed(uid: String) {
let handle = Self::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::ProfileUpdateCompleted { uid });
} else {
log::warn!("Notification system not initialized when trying to send ProfileUpdateCompleted event.");
}
}
/// 通知前端显示消息队列
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
let handle = Self::global();
let status_str = status.into();
let msg_str = msg.into();
if !*handle.startup_completed.read() {
logging!(
info,
Type::Frontend,
true,
"启动过程中发现错误,加入消息队列: {} - {}",
status_str,
msg_str
);
let mut errors = handle.startup_errors.write();
errors.push(ErrorMessage {
status: status_str,
message: msg_str,
});
return;
}
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
system.send_event(FrontendEvent::NoticeMessage {
status: status_str,
message: msg_str,
});
}
}
pub fn mark_startup_completed(&self) {
{
let mut completed = self.startup_completed.write();
*completed = true;
}
self.send_startup_errors();
}
/// 发送启动时累积的所有错误消息
fn send_startup_errors(&self) {
let errors = {
let mut errors = self.startup_errors.write();
std::mem::take(&mut *errors)
};
if errors.is_empty() {
return;
}
logging!(
info,
Type::Frontend,
true,
"发送{}条启动时累积的错误消息",
errors.len()
);
// 启动单独线程处理启动错误,避免阻塞主线程
let thread_result = thread::Builder::new()
.name("startup-errors-sender".into())
.spawn(move || {
thread::sleep(Duration::from_secs(2));
let handle = Handle::global();
if handle.is_exiting() {
return;
}
let system_opt = handle.notification_system.read();
if let Some(system) = system_opt.as_ref() {
for error in errors {
if handle.is_exiting() {
break;
}
system.send_event(FrontendEvent::NoticeMessage {
status: error.status,
message: error.message,
});
thread::sleep(Duration::from_millis(300));
}
}
});
if let Err(e) = thread_result {
log::error!("Failed to spawn startup errors thread: {}", e);
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
}
}
pub fn set_is_exiting(&self) {
let mut is_exiting = self.is_exiting.write();
*is_exiting = true;
}
let mut system_opt = self.notification_system.write();
if let Some(system) = system_opt.as_mut() {
system.shutdown();
}
pub fn set_core_process(&self, process: CommandChild) {
let mut core_process = self.core_process.write();
*core_process = Some(process);
}
pub fn take_core_process(&self) -> Option<CommandChild> {
let mut core_process = self.core_process.write();
core_process.take()
}
/// 检查是否有运行中的核心进程
pub fn has_core_process(&self) -> bool {
self.core_process.read().is_some()
}
pub fn is_exiting(&self) -> bool {

View File

@@ -1,17 +1,13 @@
use crate::{
config::Config,
core::handle,
feat, logging, logging_error,
module::lightweight::entry_lightweight_mode,
process::AsyncHandler,
utils::{logging::Type, resolve},
};
use crate::core::handle;
use crate::{config::Config, feat, log_err};
use crate::utils::resolve;
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use tauri::Manager;
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
use tauri::async_runtime;
pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
@@ -30,27 +26,19 @@ impl Hotkey {
let verge = Config::verge();
let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true);
logging!(
debug,
Type::Hotkey,
true,
"Initializing global hotkeys: {}",
enable_global_hotkey
);
println!("Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
// 如果全局热键被禁用,则不注册热键
if !enable_global_hotkey {
println!("Global hotkey is disabled, skipping registration");
log::info!(target: "app", "Global hotkey is disabled, skipping registration");
return Ok(());
}
if let Some(hotkeys) = verge.latest().hotkeys.as_ref() {
logging!(
debug,
Type::Hotkey,
true,
"Has {} hotkeys need to register",
hotkeys.len()
);
println!("Found {} hotkeys to register", hotkeys.len());
log::info!(target: "app", "Found {} hotkeys to register", hotkeys.len());
for hotkey in hotkeys.iter() {
let mut iter = hotkey.split(',');
@@ -59,51 +47,28 @@ impl Hotkey {
match (key, func) {
(Some(key), Some(func)) => {
logging!(
debug,
Type::Hotkey,
true,
"Registering hotkey: {} -> {}",
key,
func
);
println!("Registering hotkey: {} -> {}", key, func);
log::info!(target: "app", "Registering hotkey: {} -> {}", key, func);
if let Err(e) = self.register(key, func) {
logging!(
error,
Type::Hotkey,
true,
"Failed to register hotkey {} -> {}: {:?}",
key,
func,
e
);
println!("Failed to register hotkey {} -> {}: {:?}", key, func, e);
log::error!(target: "app", "Failed to register hotkey {} -> {}: {:?}", key, func, e);
} else {
logging!(
debug,
Type::Hotkey,
"Successfully registered hotkey {} -> {}",
key,
func
);
println!("Successfully registered hotkey {} -> {}", key, func);
log::info!(target: "app", "Successfully registered hotkey {} -> {}", key, func);
}
}
_ => {
let key = key.unwrap_or("None");
let func = func.unwrap_or("None");
logging!(
error,
Type::Hotkey,
true,
"Invalid hotkey configuration: `{}`:`{}`",
key,
func
);
println!("Invalid hotkey configuration: `{key}`:`{func}`");
log::error!(target: "app", "Invalid hotkey configuration: `{key}`:`{func}`");
}
}
}
self.current.lock().clone_from(hotkeys);
} else {
logging!(debug, Type::Hotkey, "No hotkeys configured");
println!("No hotkeys configured");
log::info!(target: "app", "No hotkeys configured");
}
Ok(())
@@ -120,129 +85,67 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
logging!(
debug,
Type::Hotkey,
"Attempting to register hotkey: {} for function: {}",
hotkey,
func
);
println!("Attempting to register hotkey: {} for function: {}", hotkey, func);
log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func);
if manager.is_registered(hotkey) {
logging!(
debug,
Type::Hotkey,
"Hotkey {} was already registered, unregistering first",
hotkey
);
println!("Hotkey {} was already registered, unregistering first", hotkey);
log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey);
manager.unregister(hotkey)?;
}
let f = match func.trim() {
"open_or_close_dashboard" => {
logging!(
debug,
Type::Hotkey,
"Registering open_or_close_dashboard function"
);
println!("Registering open_or_close_dashboard function");
log::info!(target: "app", "Registering open_or_close_dashboard function");
|| {
logging!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation Start ==="
);
// 检查是否在轻量模式下,如果是,需要同步处理
if crate::module::lightweight::is_in_lightweight_mode() {
logging!(
info,
Type::Hotkey,
true,
"In lightweight mode, calling open_or_close_dashboard directly"
);
crate::feat::open_or_close_dashboard();
} else {
AsyncHandler::spawn(move || async move {
logging!(
debug,
Type::Hotkey,
true,
"Toggle dashboard window visibility (async)"
);
// 检查窗口是否存在
if let Some(window) = handle::Handle::global().get_window() {
// 如果窗口可见,则隐藏
match window.is_visible() {
Ok(visible) => {
if visible {
logging!(
info,
Type::Window,
true,
"Window is visible, hiding it"
);
let _ = window.hide();
} else {
// 如果窗口不可见,则显示
logging!(
info,
Type::Window,
true,
"Window is hidden, showing it"
);
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
let _ = window.show();
let _ = window.set_focus();
}
}
Err(e) => {
logging!(
warn,
Type::Window,
true,
"Failed to check window visibility: {}",
e
);
let _ = window.show();
let _ = window.set_focus();
}
}
println!("=== Hotkey Dashboard Window Operation Start ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation Start ===");
// 使用 spawn_blocking 来确保在正确的线程上执行
async_runtime::spawn_blocking(|| {
println!("Toggle dashboard window visibility");
log::info!(target: "app", "Toggle dashboard window visibility");
// 检查窗口是否存在
if let Some(window) = handle::Handle::global().get_window() {
// 如果窗口可见,则隐藏它
if window.is_visible().unwrap_or(false) {
println!("Window is visible, hiding it");
log::info!(target: "app", "Window is visible, hiding it");
let _ = window.hide();
} else {
// 如果窗口不存在,创建一个新窗口
logging!(
info,
Type::Window,
true,
"Window does not exist, creating a new one"
);
resolve::create_window(true);
// 如果窗口不可见,则显示它
println!("Window is hidden, showing it");
log::info!(target: "app", "Window is hidden, showing it");
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
let _ = window.show();
let _ = window.set_focus();
}
});
}
logging!(
debug,
Type::Hotkey,
"=== Hotkey Dashboard Window Operation End ==="
);
} else {
// 如果窗口不存在,创建一个新窗口
println!("Window does not exist, creating a new one");
log::info!(target: "app", "Window does not exist, creating a new one");
resolve::create_window();
}
});
println!("=== Hotkey Dashboard Window Operation End ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ===");
}
}
},
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"toggle_system_proxy" => || feat::toggle_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(None),
"entry_lightweight_mode" => || entry_lightweight_mode(),
"quit" => || feat::quit(),
#[cfg(target_os = "macos")]
"hide" => || feat::hide(),
"quit" => || feat::quit(Some(0)),
_ => {
logging!(error, Type::Hotkey, "Invalid function: {}", func);
println!("Invalid function: {}", func);
log::error!(target: "app", "Invalid function: {}", func);
bail!("invalid function \"{func}\"");
}
};
@@ -251,33 +154,34 @@ impl Hotkey {
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey, event| {
if event.state == ShortcutState::Pressed {
logging!(debug, Type::Hotkey, "Hotkey pressed: {:?}", hotkey);
println!("Hotkey pressed: {:?}", hotkey);
log::info!(target: "app", "Hotkey pressed: {:?}", hotkey);
if hotkey.key == Code::KeyQ && is_quit {
if let Some(window) = app_handle.get_webview_window("main") {
if window.is_focused().unwrap_or(false) {
logging!(debug, Type::Hotkey, "Executing quit function");
println!("Executing quit function");
log::info!(target: "app", "Executing quit function");
f();
}
}
} else {
// 直接执行函数,不做任何状态检查
logging!(debug, Type::Hotkey, "Executing function directly");
// 获取全局热键状态
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if is_enable_global_hotkey {
println!("Executing function directly");
log::info!(target: "app", "Executing function directly");
// 获取轻量模式状态和全局热键状态
let is_lite_mode = Config::verge().latest().enable_lite_mode.unwrap_or(false);
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
// 在轻量模式下或配置了全局热键时,始终执行热键功能
if is_lite_mode || is_enable_global_hotkey {
f();
} else {
use crate::utils::window_manager::WindowManager;
} else if let Some(window) = app_handle.get_webview_window("main") {
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
let is_visible = WindowManager::is_main_window_visible();
let is_focused = WindowManager::is_main_window_focused();
let is_visible = window.is_visible().unwrap_or(false);
let is_focused = window.is_focused().unwrap_or(false);
if is_focused && is_visible {
f();
}
@@ -286,13 +190,8 @@ impl Hotkey {
}
});
logging!(
debug,
Type::Hotkey,
"Successfully registered hotkey {} for {}",
hotkey,
func
);
println!("Successfully registered hotkey {} for {}", hotkey, func);
log::info!(target: "app", "Successfully registered hotkey {} for {}", hotkey, func);
Ok(())
}
@@ -300,7 +199,7 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
manager.unregister(hotkey)?;
logging!(debug, Type::Hotkey, "Unregister hotkey {}", hotkey);
log::debug!(target: "app", "unregister hotkey {hotkey}");
Ok(())
}
@@ -316,7 +215,7 @@ impl Hotkey {
});
add.iter().for_each(|(key, func)| {
logging_error!(Type::Hotkey, self.register(key, func));
log_err!(self.register(key, func));
});
*current = new_hotkeys;
@@ -331,9 +230,9 @@ impl Hotkey {
let func = iter.next();
let key = iter.next();
if let (Some(func), Some(key)) = (func, key) {
let func = func.trim();
let key = key.trim();
if func.is_some() && key.is_some() {
let func = func.unwrap().trim();
let key = key.unwrap().trim();
map.insert(key, func);
}
});
@@ -373,13 +272,7 @@ impl Drop for Hotkey {
fn drop(&mut self) {
let app_handle = handle::Handle::global().app_handle().unwrap();
if let Err(e) = app_handle.global_shortcut().unregister_all() {
logging!(
error,
Type::Hotkey,
true,
"Error unregistering all hotkeys: {:?}",
e
);
log::error!(target:"app", "Error unregistering all hotkeys: {:?}", e);
}
}
}

View File

@@ -4,10 +4,9 @@ mod core;
pub mod handle;
pub mod hotkey;
pub mod service;
pub mod service_ipc;
pub mod sysopt;
pub mod timer;
pub mod tray;
pub mod win_uwp;
pub use self::{core::*, timer::Timer};
pub use self::core::*;

File diff suppressed because it is too large Load Diff

View File

@@ -1,340 +0,0 @@
use crate::{logging, utils::logging::Type};
use anyhow::{bail, Context, Result};
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
r"\\.\pipe\clash-verge-service"
} else {
"/tmp/clash-verge-service.sock"
};
// 定义命令类型
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum IpcCommand {
GetClash,
GetVersion,
StartClash,
StopClash,
}
// IPC消息格式
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpcRequest {
pub id: String,
pub timestamp: u64,
pub command: IpcCommand,
pub payload: serde_json::Value,
pub signature: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpcResponse {
pub id: String,
pub success: bool,
pub data: Option<serde_json::Value>,
pub error: Option<String>,
pub signature: String,
}
// 密钥派生函数
fn derive_secret_key() -> Vec<u8> {
// to do
// 从系统安全存储中获取或从程序安装时生成的密钥文件中读取
let unique_app_id = "clash-verge-app-secret-fuck-me-until-daylight";
let mut hasher = Sha256::new();
hasher.update(unique_app_id.as_bytes());
hasher.finalize().to_vec()
}
// 创建带签名的请求
pub fn create_signed_request(
command: IpcCommand,
payload: serde_json::Value,
) -> Result<IpcRequest> {
let id = nanoid::nanoid!(32);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let unsigned_request = IpcRequest {
id: id.clone(),
timestamp,
command: command.clone(),
payload: payload.clone(),
signature: String::new(),
};
let unsigned_json = serde_json::to_string(&unsigned_request)?;
let signature = sign_message(&unsigned_json)?;
Ok(IpcRequest {
id,
timestamp,
command,
payload,
signature,
})
}
// 签名消息
fn sign_message(message: &str) -> Result<String> {
type HmacSha256 = Hmac<Sha256>;
let secret_key = derive_secret_key();
let mut mac = HmacSha256::new_from_slice(&secret_key).context("HMAC初始化失败")?;
mac.update(message.as_bytes());
let result = mac.finalize();
let signature = hex::encode(result.into_bytes());
Ok(signature)
}
// 验证响应签名
pub fn verify_response_signature(response: &IpcResponse) -> Result<bool> {
let verification_response = IpcResponse {
id: response.id.clone(),
success: response.success,
data: response.data.clone(),
error: response.error.clone(),
signature: String::new(),
};
let message = serde_json::to_string(&verification_response)?;
let expected_signature = sign_message(&message)?;
Ok(expected_signature == response.signature)
}
// IPC连接管理-win
#[cfg(target_os = "windows")]
pub async fn send_ipc_request(
command: IpcCommand,
payload: serde_json::Value,
) -> Result<IpcResponse> {
use std::{
ffi::CString,
fs::File,
io::{Read, Write},
os::windows::io::{FromRawHandle, RawHandle},
ptr,
};
use winapi::um::{
fileapi::{CreateFileA, OPEN_EXISTING},
handleapi::INVALID_HANDLE_VALUE,
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
};
logging!(info, Type::Service, true, "正在连接服务 (Windows)...");
let command_type = format!("{:?}", command);
let request = match create_signed_request(command, payload) {
Ok(req) => req,
Err(e) => {
logging!(error, Type::Service, true, "创建签名请求失败: {}", e);
return Err(e);
}
};
let request_json = serde_json::to_string(&request)?;
let result = tokio::task::spawn_blocking(move || -> Result<IpcResponse> {
let c_pipe_name = match CString::new(IPC_SOCKET_NAME) {
Ok(name) => name,
Err(e) => {
logging!(error, Type::Service, true, "创建CString失败: {}", e);
return Err(anyhow::anyhow!("创建CString失败: {}", e));
}
};
let handle = unsafe {
CreateFileA(
c_pipe_name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
let error = std::io::Error::last_os_error();
logging!(
error,
Type::Service,
true,
"连接到服务命名管道失败: {}",
error
);
return Err(anyhow::anyhow!("无法连接到服务命名管道: {}", error));
}
let mut pipe = unsafe { File::from_raw_handle(handle as RawHandle) };
logging!(info, Type::Service, true, "服务连接成功 (Windows)");
let request_bytes = request_json.as_bytes();
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
if let Err(e) = pipe.write_all(&len_bytes) {
logging!(error, Type::Service, true, "写入请求长度失败: {}", e);
return Err(anyhow::anyhow!("写入请求长度失败: {}", e));
}
if let Err(e) = pipe.write_all(request_bytes) {
logging!(error, Type::Service, true, "写入请求内容失败: {}", e);
return Err(anyhow::anyhow!("写入请求内容失败: {}", e));
}
if let Err(e) = pipe.flush() {
logging!(error, Type::Service, true, "刷新管道失败: {}", e);
return Err(anyhow::anyhow!("刷新管道失败: {}", e));
}
let mut response_len_bytes = [0u8; 4];
if let Err(e) = pipe.read_exact(&mut response_len_bytes) {
logging!(error, Type::Service, true, "读取响应长度失败: {}", e);
return Err(anyhow::anyhow!("读取响应长度失败: {}", e));
}
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
let mut response_bytes = vec![0u8; response_len];
if let Err(e) = pipe.read_exact(&mut response_bytes) {
logging!(error, Type::Service, true, "读取响应内容失败: {}", e);
return Err(anyhow::anyhow!("读取响应内容失败: {}", e));
}
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
Ok(r) => r,
Err(e) => {
logging!(error, Type::Service, true, "服务响应解析失败: {}", e);
return Err(anyhow::anyhow!("解析响应失败: {}", e));
}
};
match verify_response_signature(&response) {
Ok(valid) => {
if !valid {
logging!(error, Type::Service, true, "服务响应签名验证失败");
bail!("服务响应签名验证失败");
}
}
Err(e) => {
logging!(error, Type::Service, true, "验证响应签名时出错: {}", e);
return Err(e);
}
}
logging!(
info,
Type::Service,
true,
"IPC请求完成: 命令={}, 成功={}",
command_type,
response.success
);
Ok(response)
})
.await??;
Ok(result)
}
// IPC连接管理-unix
#[cfg(target_family = "unix")]
pub async fn send_ipc_request(
command: IpcCommand,
payload: serde_json::Value,
) -> Result<IpcResponse> {
use std::os::unix::net::UnixStream;
logging!(info, Type::Service, true, "正在连接服务 (Unix)...");
let command_type = format!("{:?}", command);
let request = match create_signed_request(command, payload) {
Ok(req) => req,
Err(e) => {
logging!(error, Type::Service, true, "创建签名请求失败: {}", e);
return Err(e);
}
};
let request_json = serde_json::to_string(&request)?;
let mut stream = match UnixStream::connect(IPC_SOCKET_NAME) {
Ok(s) => {
logging!(info, Type::Service, true, "服务连接成功 (Unix)");
s
}
Err(e) => {
logging!(error, Type::Service, true, "连接到Unix套接字失败: {}", e);
return Err(anyhow::anyhow!("无法连接到服务Unix套接字: {}", e));
}
};
let request_bytes = request_json.as_bytes();
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
if let Err(e) = std::io::Write::write_all(&mut stream, &len_bytes) {
logging!(error, Type::Service, true, "写入请求长度失败: {}", e);
return Err(anyhow::anyhow!("写入请求长度失败: {}", e));
}
if let Err(e) = std::io::Write::write_all(&mut stream, request_bytes) {
logging!(error, Type::Service, true, "写入请求内容失败: {}", e);
return Err(anyhow::anyhow!("写入请求内容失败: {}", e));
}
let mut response_len_bytes = [0u8; 4];
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_len_bytes) {
logging!(error, Type::Service, true, "读取响应长度失败: {}", e);
return Err(anyhow::anyhow!("读取响应长度失败: {}", e));
}
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
let mut response_bytes = vec![0u8; response_len];
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_bytes) {
logging!(error, Type::Service, true, "读取响应内容失败: {}", e);
return Err(anyhow::anyhow!("读取响应内容失败: {}", e));
}
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
Ok(r) => r,
Err(e) => {
logging!(error, Type::Service, true, "服务响应解析失败: {}", e,);
return Err(anyhow::anyhow!("解析响应失败: {}", e));
}
};
match verify_response_signature(&response) {
Ok(valid) => {
if !valid {
logging!(error, Type::Service, true, "服务响应签名验证失败");
bail!("服务响应签名验证失败");
}
}
Err(e) => {
logging!(error, Type::Service, true, "验证响应签名时出错: {}", e);
return Err(e);
}
}
logging!(
info,
Type::Service,
true,
"IPC请求完成: 命令={}, 成功={}",
command_type,
response.success
);
Ok(response)
}

View File

@@ -1,11 +1,7 @@
#[cfg(target_os = "windows")]
use crate::utils::autostart as startup_shortcut;
use crate::core::handle::Handle;
use crate::{
config::{Config, IVerge},
core::handle::Handle,
logging, logging_error,
process::AsyncHandler,
utils::logging::Type,
log_err,
};
use anyhow::Result;
use once_cell::sync::OnceCell;
@@ -19,6 +15,8 @@ use tokio::time::{sleep, Duration};
pub struct Sysopt {
update_sysproxy: Arc<TokioMutex<bool>>,
reset_sysproxy: Arc<TokioMutex<bool>>,
/// helps to auto launch the app
auto_launch: Arc<Mutex<bool>>,
/// record whether the guard async is running or not
guard_state: Arc<Mutex<bool>>,
}
@@ -59,6 +57,7 @@ impl Sysopt {
SYSOPT.get_or_init(|| Sysopt {
update_sysproxy: Arc::new(TokioMutex::new(false)),
reset_sysproxy: Arc::new(TokioMutex::new(false)),
auto_launch: Arc::new(Mutex::new(false)),
guard_state: Arc::new(false.into()),
})
}
@@ -78,16 +77,12 @@ impl Sysopt {
.unwrap_or(Config::clash().data().get_mixed_port());
let pac_port = IVerge::get_singleton_port();
let (sys_enable, pac_enable, proxy_host) = {
let (sys_enable, pac_enable) = {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.unwrap_or(false),
verge.proxy_auto_config.unwrap_or(false),
verge
.proxy_host
.clone()
.unwrap_or_else(|| String::from("127.0.0.1")),
)
};
@@ -95,13 +90,13 @@ impl Sysopt {
{
let mut sys = Sysproxy {
enable: false,
host: proxy_host.clone(),
host: String::from("127.0.0.1"),
port,
bypass: get_bypass(),
};
let mut auto = Autoproxy {
enable: false,
url: format!("http://{}:{}/commands/pac", proxy_host, pac_port),
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
if !sys_enable {
@@ -131,7 +126,8 @@ impl Sysopt {
if !sys_enable {
return self.reset_sysproxy().await;
}
use crate::{core::handle::Handle, utils::dirs};
use crate::core::handle::Handle;
use crate::utils::dirs;
use anyhow::bail;
use tauri_plugin_shell::ShellExt;
@@ -145,7 +141,7 @@ impl Sysopt {
let shell = app_handle.shell();
let output = if pac_enable {
let address = format!("http://{}:{}/commands/pac", proxy_host, pac_port);
let address = format!("http://{}:{}/commands/pac", "127.0.0.1", pac_port);
let output = shell
.command(sysproxy_exe.as_path().to_str().unwrap())
.args(["pac", address.as_str()])
@@ -154,7 +150,7 @@ impl Sysopt {
.unwrap();
output
} else {
let address = format!("{}:{}", proxy_host, port);
let address = format!("{}:{}", "127.0.0.1", port);
let bypass = get_bypass();
let output = shell
.command(sysproxy_exe.as_path().to_str().unwrap())
@@ -189,7 +185,8 @@ impl Sysopt {
#[cfg(target_os = "windows")]
{
use crate::{core::handle::Handle, utils::dirs};
use crate::core::handle::Handle;
use crate::utils::dirs;
use anyhow::bail;
use tauri_plugin_shell::ShellExt;
@@ -220,93 +217,31 @@ impl Sysopt {
/// update the startup
pub fn update_launch(&self) -> Result<()> {
let enable_auto_launch = { Config::verge().latest().enable_auto_launch };
let is_enable = enable_auto_launch.unwrap_or(false);
logging!(info, true, "Setting auto-launch state to: {:?}", is_enable);
// 首先尝试使用快捷方式方法
#[cfg(target_os = "windows")]
{
if is_enable {
if let Err(e) = startup_shortcut::create_shortcut() {
log::error!(target: "app", "创建启动快捷方式失败: {}", e);
// 如果快捷方式创建失败,回退到原来的方法
self.try_original_autostart_method(is_enable);
} else {
return Ok(());
}
} else if let Err(e) = startup_shortcut::remove_shortcut() {
log::error!(target: "app", "删除启动快捷方式失败: {}", e);
self.try_original_autostart_method(is_enable);
} else {
return Ok(());
}
}
#[cfg(not(target_os = "windows"))]
{
// 非Windows平台使用原来的方法
self.try_original_autostart_method(is_enable);
}
let _lock = self.auto_launch.lock();
let enable = { Config::verge().latest().enable_auto_launch };
let enable = enable.unwrap_or(false);
let app_handle = Handle::global().app_handle().unwrap();
let autostart_manager = app_handle.autolaunch();
println!("enable: {}", enable);
match enable {
true => log_err!(autostart_manager.enable()),
false => log_err!(autostart_manager.disable()),
};
Ok(())
}
/// 尝试使用原来的自启动方法
fn try_original_autostart_method(&self, is_enable: bool) {
let app_handle = Handle::global().app_handle().unwrap();
let autostart_manager = app_handle.autolaunch();
if is_enable {
logging_error!(Type::System, true, "{:?}", autostart_manager.enable());
} else {
logging_error!(Type::System, true, "{:?}", autostart_manager.disable());
}
}
/// 获取当前自启动的实际状态
pub fn get_launch_status(&self) -> Result<bool> {
// 首先尝试检查快捷方式是否存在
#[cfg(target_os = "windows")]
{
match startup_shortcut::is_shortcut_enabled() {
Ok(enabled) => {
log::info!(target: "app", "快捷方式自启动状态: {}", enabled);
return Ok(enabled);
}
Err(e) => {
log::error!(target: "app", "检查快捷方式失败,尝试原来的方法: {}", e);
}
}
}
// 回退到原来的方法
let app_handle = Handle::global().app_handle().unwrap();
let autostart_manager = app_handle.autolaunch();
match autostart_manager.is_enabled() {
Ok(status) => {
log::info!(target: "app", "Auto launch status: {}", status);
Ok(status)
}
Err(e) => {
log::error!(target: "app", "Failed to get auto launch status: {}", e);
Err(anyhow::anyhow!("Failed to get auto launch status: {}", e))
}
}
}
fn guard_proxy(&self) {
let _lock = self.guard_state.lock();
AsyncHandler::spawn(move || async move {
tauri::async_runtime::spawn(async move {
// default duration is 10s
let mut wait_secs = 10u64;
loop {
sleep(Duration::from_secs(wait_secs)).await;
let (enable, guard, guard_duration, pac, proxy_host) = {
let (enable, guard, guard_duration, pac) = {
let verge = Config::verge();
let verge = verge.latest();
(
@@ -314,10 +249,6 @@ impl Sysopt {
verge.enable_proxy_guard.unwrap_or(false),
verge.proxy_guard_duration.unwrap_or(10),
verge.proxy_auto_config.unwrap_or(false),
verge
.proxy_host
.clone()
.unwrap_or_else(|| String::from("127.0.0.1")),
)
};
@@ -331,130 +262,86 @@ impl Sysopt {
log::debug!(target: "app", "try to guard the system proxy");
// 获取期望的代理端口
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let sysproxy = Sysproxy::get_system_proxy();
let autoproxy = Autoproxy::get_auto_proxy();
if sysproxy.is_err() || autoproxy.is_err() {
log::error!(target: "app", "failed to get the system proxy");
continue;
}
let sysproxy_enable = sysproxy.ok().map(|s| s.enable).unwrap_or(false);
let autoproxy_enable = autoproxy.ok().map(|s| s.enable).unwrap_or(false);
if sysproxy_enable || autoproxy_enable {
continue;
}
let port = {
Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port())
};
let pac_port = IVerge::get_singleton_port();
let bypass = get_bypass();
#[cfg(not(target_os = "windows"))]
{
if pac {
let autoproxy = Autoproxy {
enable: true,
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
log_err!(autoproxy.set_auto_proxy());
} else {
let sysproxy = Sysproxy {
enable: true,
host: "127.0.0.1".into(),
port,
bypass: get_bypass(),
};
// 检查系统代理配置
if pac {
// 检查 PAC 代理设置
let expected_url = format!("http://{}:{}/commands/pac", proxy_host, pac_port);
let autoproxy = match Autoproxy::get_auto_proxy() {
Ok(ap) => ap,
Err(e) => {
log::error!(target: "app", "failed to get the auto proxy: {}", e);
continue;
}
};
// 检查自动代理是否启用且URL是否正确
if !autoproxy.enable || autoproxy.url != expected_url {
log::info!(target: "app", "auto proxy settings changed, restoring...");
#[cfg(not(target_os = "windows"))]
{
let new_autoproxy = Autoproxy {
enable: true,
url: expected_url,
};
logging_error!(Type::System, true, new_autoproxy.set_auto_proxy());
}
#[cfg(target_os = "windows")]
{
use crate::{core::handle::Handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap();
let binary_path = match dirs::service_path() {
Ok(path) => path,
Err(e) => {
log::error!(target: "app", "failed to get service path: {}", e);
continue;
}
};
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
if !sysproxy_exe.exists() {
log::error!(target: "app", "sysproxy.exe not found");
continue;
}
let shell = app_handle.shell();
let output = shell
.command(sysproxy_exe.as_path().to_str().unwrap())
.args(["pac", expected_url.as_str()])
.output()
.await
.unwrap();
if !output.status.success() {
log::error!(target: "app", "failed to set auto proxy");
}
}
}
} else {
// 检查常规系统代理设置
let sysproxy = match Sysproxy::get_system_proxy() {
Ok(sp) => sp,
Err(e) => {
log::error!(target: "app", "failed to get the system proxy: {}", e);
continue;
}
};
// 检查系统代理是否启用且配置是否匹配
if !sysproxy.enable || sysproxy.host != proxy_host || sysproxy.port != port {
log::info!(target: "app", "system proxy settings changed, restoring...");
#[cfg(not(target_os = "windows"))]
{
let new_sysproxy = Sysproxy {
enable: true,
host: proxy_host.clone(),
port,
bypass: bypass.clone(),
};
logging_error!(Type::System, true, new_sysproxy.set_system_proxy());
}
#[cfg(target_os = "windows")]
{
use crate::{core::handle::Handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap();
let binary_path = match dirs::service_path() {
Ok(path) => path,
Err(e) => {
log::error!(target: "app", "failed to get service path: {}", e);
continue;
}
};
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
if !sysproxy_exe.exists() {
log::error!(target: "app", "sysproxy.exe not found");
continue;
}
let address = format!("{}:{}", proxy_host, port);
let shell = app_handle.shell();
let output = shell
.command(sysproxy_exe.as_path().to_str().unwrap())
.args(["global", address.as_str(), bypass.as_ref()])
.output()
.await
.unwrap();
if !output.status.success() {
log::error!(target: "app", "failed to set system proxy");
}
}
log_err!(sysproxy.set_system_proxy());
}
}
#[cfg(target_os = "windows")]
{
use crate::core::handle::Handle;
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap();
let binary_path = dirs::service_path().unwrap();
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
if !sysproxy_exe.exists() {
break;
}
let shell = app_handle.shell();
let output = if pac {
let address = format!("http://{}:{}/commands/pac", "127.0.0.1", pac_port);
shell
.command(sysproxy_exe.as_path().to_str().unwrap())
.args(["pac", address.as_str()])
.output()
.await
.unwrap()
} else {
let address = format!("{}:{}", "127.0.0.1", port);
let bypass = get_bypass();
shell
.command(sysproxy_exe.as_path().to_str().unwrap())
.args(["global", address.as_str(), bypass.as_ref()])
.output()
.await
.unwrap()
};
if !output.status.success() {
break;
}
};
}
});
}

View File

@@ -1,32 +1,24 @@
use crate::{config::Config, feat, logging, logging_error, utils::logging::Type};
use crate::config::Config;
use crate::feat;
use crate::core::CoreManager;
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, sync::Arc};
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
type TaskID = u64;
#[derive(Debug, Clone)]
pub struct TimerTask {
pub task_id: TaskID,
pub interval_minutes: u64,
#[allow(unused)]
pub last_run: i64, // Timestamp of last execution
}
pub struct Timer {
/// cron manager
pub delay_timer: Arc<RwLock<DelayTimer>>,
delay_timer: Arc<Mutex<DelayTimer>>,
/// save the current state - using RwLock for better read concurrency
pub timer_map: Arc<RwLock<HashMap<String, TimerTask>>>,
/// save the current state
timer_map: Arc<Mutex<HashMap<String, (TaskID, u64)>>>,
/// increment id - kept as mutex since it's just a counter
pub timer_count: Arc<Mutex<TaskID>>,
/// Flag to mark if timer is initialized - atomic for better performance
pub initialized: Arc<std::sync::atomic::AtomicBool>,
/// increment id
timer_count: Arc<Mutex<TaskID>>,
}
impl Timer {
@@ -34,190 +26,68 @@ impl Timer {
static TIMER: OnceCell<Timer> = OnceCell::new();
TIMER.get_or_init(|| Timer {
delay_timer: Arc::new(RwLock::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(RwLock::new(HashMap::new())),
delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(Mutex::new(HashMap::new())),
timer_count: Arc::new(Mutex::new(1)),
initialized: Arc::new(std::sync::atomic::AtomicBool::new(false)),
})
}
/// Initialize timer with better error handling and atomic operations
/// restore timer
pub fn init(&self) -> Result<()> {
// Use compare_exchange for thread-safe initialization check
if self
.initialized
.compare_exchange(
false,
true,
std::sync::atomic::Ordering::SeqCst,
std::sync::atomic::Ordering::SeqCst,
)
.is_err()
{
logging!(debug, Type::Timer, "Timer already initialized, skipping...");
return Ok(());
}
logging!(info, Type::Timer, true, "Initializing timer...");
// Initialize timer tasks
if let Err(e) = self.refresh() {
// Reset initialization flag on error
self.initialized
.store(false, std::sync::atomic::Ordering::SeqCst);
logging_error!(Type::Timer, false, "Failed to initialize timer: {}", e);
return Err(e);
}
let timer_map = self.timer_map.read();
logging!(
info,
Type::Timer,
"已注册的定时任务数量: {}",
timer_map.len()
);
for (uid, task) in timer_map.iter() {
logging!(
info,
Type::Timer,
"注册了定时任务 - uid={}, interval={}min, task_id={}",
uid,
task.interval_minutes,
task.task_id
);
}
self.refresh()?;
let cur_timestamp = chrono::Local::now().timestamp();
// Collect profiles that need immediate update
let profiles_to_update = if let Some(items) = Config::profiles().latest().get_items() {
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
if let Some(items) = Config::profiles().latest().get_items() {
items
.iter()
.filter_map(|item| {
let interval = item.option.as_ref()?.update_interval? as i64;
// mins to seconds
let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60;
let updated = item.updated? as i64;
let uid = item.uid.as_ref()?;
if interval > 0 && cur_timestamp - updated >= interval * 60 {
logging!(info, Type::Timer, "需要立即更新的配置: uid={}", uid);
Some(uid.clone())
if interval > 0 && cur_timestamp - updated >= interval {
Some(item)
} else {
None
}
})
.collect::<Vec<String>>()
} else {
Vec::new()
};
// Advance tasks outside of locks to minimize lock contention
if !profiles_to_update.is_empty() {
logging!(
info,
Type::Timer,
"需要立即更新的配置数量: {}",
profiles_to_update.len()
);
let timer_map = self.timer_map.read();
let delay_timer = self.delay_timer.write();
for uid in profiles_to_update {
if let Some(task) = timer_map.get(&uid) {
logging!(info, Type::Timer, "立即执行任务: uid={}", uid);
if let Err(e) = delay_timer.advance_task(task.task_id) {
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
.for_each(|item| {
if let Some(uid) = item.uid.as_ref() {
if let Some((task_id, _)) = timer_map.get(uid) {
crate::log_err!(delay_timer.advance_task(*task_id));
}
}
}
}
})
}
logging!(info, Type::Timer, "Timer initialization completed");
Ok(())
}
/// Refresh timer tasks with better error handling
/// Correctly update all cron tasks
pub fn refresh(&self) -> Result<()> {
// Generate diff outside of lock to minimize lock contention
let diff_map = self.gen_diff();
if diff_map.is_empty() {
logging!(debug, Type::Timer, "No timer changes needed");
return Ok(());
}
let mut timer_map = self.timer_map.lock();
let mut delay_timer = self.delay_timer.lock();
logging!(
info,
Type::Timer,
"Refreshing {} timer tasks",
diff_map.len()
);
// Apply changes while holding locks
let mut timer_map = self.timer_map.write();
let mut delay_timer = self.delay_timer.write();
for (uid, diff) in diff_map {
for (uid, diff) in diff_map.into_iter() {
match diff {
DiffFlag::Del(tid) => {
timer_map.remove(&uid);
if let Err(e) = delay_timer.remove_task(tid) {
logging!(
warn,
Type::Timer,
"Failed to remove task {} for uid {}: {}",
tid,
uid,
e
);
} else {
logging!(debug, Type::Timer, "Removed task {} for uid {}", tid, uid);
}
let _ = timer_map.remove(&uid);
crate::log_err!(delay_timer.remove_task(tid));
}
DiffFlag::Add(tid, interval) => {
let task = TimerTask {
task_id: tid,
interval_minutes: interval,
last_run: chrono::Local::now().timestamp(),
};
timer_map.insert(uid.clone(), task);
if let Err(e) = self.add_task(&mut delay_timer, uid.clone(), tid, interval) {
logging_error!(Type::Timer, "Failed to add task for uid {}: {}", uid, e);
timer_map.remove(&uid); // Rollback on failure
} else {
logging!(debug, Type::Timer, "Added task {} for uid {}", tid, uid);
}
DiffFlag::Add(tid, val) => {
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(self.add_task(&mut delay_timer, uid, tid, val));
}
DiffFlag::Mod(tid, interval) => {
// Remove old task first
if let Err(e) = delay_timer.remove_task(tid) {
logging!(
warn,
Type::Timer,
"Failed to remove old task {} for uid {}: {}",
tid,
uid,
e
);
}
// Then add the new one
let task = TimerTask {
task_id: tid,
interval_minutes: interval,
last_run: chrono::Local::now().timestamp(),
};
timer_map.insert(uid.clone(), task);
if let Err(e) = self.add_task(&mut delay_timer, uid.clone(), tid, interval) {
logging_error!(Type::Timer, "Failed to update task for uid {}: {}", uid, e);
timer_map.remove(&uid); // Rollback on failure
} else {
logging!(debug, Type::Timer, "Updated task {} for uid {}", tid, uid);
}
DiffFlag::Mod(tid, val) => {
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(delay_timer.remove_task(tid));
crate::log_err!(self.add_task(&mut delay_timer, uid, tid, val));
}
}
}
@@ -225,106 +95,59 @@ impl Timer {
Ok(())
}
/// Generate map of profile UIDs to update intervals
/// generate a uid -> update_interval map
fn gen_map(&self) -> HashMap<String, u64> {
let mut new_map = HashMap::new();
if let Some(items) = Config::profiles().latest().get_items() {
for item in items.iter() {
if let Some(option) = item.option.as_ref() {
if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) {
if interval > 0 {
logging!(
debug,
Type::Timer,
"找到定时更新配置: uid={}, interval={}min",
uid,
interval
);
new_map.insert(uid.clone(), interval);
}
if item.option.is_some() {
let option = item.option.as_ref().unwrap();
let interval = option.update_interval.unwrap_or(0);
if interval > 0 {
new_map.insert(item.uid.clone().unwrap(), interval);
}
}
}
}
logging!(
debug,
Type::Timer,
"生成的定时更新配置数量: {}",
new_map.len()
);
new_map
}
/// Generate differences between current and new timer configuration
/// generate the diff map for refresh
fn gen_diff(&self) -> HashMap<String, DiffFlag> {
let mut diff_map = HashMap::new();
let timer_map = self.timer_map.lock();
let new_map = self.gen_map();
let cur_map = &timer_map;
// Read lock for comparing current state
let timer_map = self.timer_map.read();
logging!(
debug,
Type::Timer,
"当前 timer_map 大小: {}",
timer_map.len()
);
cur_map.iter().for_each(|(uid, (tid, val))| {
let new_val = new_map.get(uid).unwrap_or(&0);
// Find tasks to modify or delete
for (uid, task) in timer_map.iter() {
match new_map.get(uid) {
Some(&interval) if interval != task.interval_minutes => {
// Task exists but interval changed
logging!(
debug,
Type::Timer,
"定时任务间隔变更: uid={}, 旧={}, 新={}",
uid,
task.interval_minutes,
interval
);
diff_map.insert(uid.clone(), DiffFlag::Mod(task.task_id, interval));
}
None => {
// Task no longer needed
logging!(debug, Type::Timer, "定时任务已删除: uid={}", uid);
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
}
_ => {
// Task exists with same interval, no change needed
logging!(debug, Type::Timer, "定时任务保持不变: uid={}", uid);
}
if *new_val == 0 {
diff_map.insert(uid.clone(), DiffFlag::Del(*tid));
} else if new_val != val {
diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val));
}
}
});
// Find new tasks to add
let mut next_id = *self.timer_count.lock();
let mut count = self.timer_count.lock();
for (uid, &interval) in new_map.iter() {
if !timer_map.contains_key(uid) {
logging!(
debug,
Type::Timer,
"新增定时任务: uid={}, interval={}min",
uid,
interval
);
diff_map.insert(uid.clone(), DiffFlag::Add(next_id, interval));
next_id += 1;
new_map.iter().for_each(|(uid, val)| {
if cur_map.get(uid).is_none() {
diff_map.insert(uid.clone(), DiffFlag::Add(*count, *val));
*count += 1;
}
}
});
// Update counter only if we added new tasks
if next_id > *self.timer_count.lock() {
*self.timer_count.lock() = next_id;
}
logging!(debug, Type::Timer, "定时任务变更数量: {}", diff_map.len());
diff_map
}
/// Add a timer task with better error handling
/// add a cron task
fn add_task(
&self,
delay_timer: &mut DelayTimer,
@@ -332,26 +155,12 @@ impl Timer {
tid: TaskID,
minutes: u64,
) -> Result<()> {
logging!(
info,
Type::Timer,
"Adding task: uid={}, id={}, interval={}min",
uid,
tid,
minutes
);
// Create a task with reasonable retries and backoff
let task = TaskBuilder::default()
.set_task_id(tid)
.set_maximum_parallel_runnable_num(1)
.set_frequency_repeated_by_minutes(minutes)
.spawn_async_routine(move || {
let uid = uid.clone();
async move {
Self::async_task(uid).await;
}
})
// .set_frequency_repeated_by_seconds(minutes) // for test
.spawn_async_routine(move || Self::async_task(uid.to_owned()))
.context("failed to create timer task")?;
delay_timer
@@ -361,118 +170,20 @@ impl Timer {
Ok(())
}
/// Get next update time for a profile
pub fn get_next_update_time(&self, uid: &str) -> Option<i64> {
logging!(info, Type::Timer, "获取下次更新时间uid={}", uid);
let timer_map = self.timer_map.read();
let task = match timer_map.get(uid) {
Some(t) => t,
None => {
logging!(warn, Type::Timer, "找不到对应的定时任务uid={}", uid);
return None;
}
};
// Get the profile updated timestamp
let profiles_config = Config::profiles();
let profiles = profiles_config.latest();
let items = match profiles.get_items() {
Some(i) => i,
None => {
logging!(warn, Type::Timer, "获取配置列表失败");
return None;
}
};
let profile = match items.iter().find(|item| item.uid.as_deref() == Some(uid)) {
Some(p) => p,
None => {
logging!(warn, Type::Timer, "找不到对应的配置uid={}", uid);
return None;
}
};
let updated = profile.updated.unwrap_or(0) as i64;
// Calculate next update time
if updated > 0 && task.interval_minutes > 0 {
let next_time = updated + (task.interval_minutes as i64 * 60);
logging!(
info,
Type::Timer,
"计算得到下次更新时间: {}, uid={}",
next_time,
uid
);
Some(next_time)
} else {
logging!(
warn,
Type::Timer,
"更新时间或间隔无效updated={}, interval={}",
updated,
task.interval_minutes
);
None
}
}
/// Emit update events for frontend notification
fn emit_update_event(_uid: &str, _is_start: bool) {
#[cfg(any(feature = "verge-dev", feature = "default"))]
{
if _is_start {
super::handle::Handle::notify_profile_update_started(_uid.to_string());
} else {
super::handle::Handle::notify_profile_update_completed(_uid.to_string());
}
}
}
/// Async task with better error handling and logging
/// the task runner
async fn async_task(uid: String) {
let task_start = std::time::Instant::now();
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
match tokio::time::timeout(std::time::Duration::from_secs(40), async {
Self::emit_update_event(&uid, true);
let is_current = Config::profiles().latest().current.as_ref() == Some(&uid);
logging!(
info,
Type::Timer,
"配置 {} 是否为当前激活配置: {}",
uid,
is_current
);
feat::update_profile(uid.clone(), None, Some(is_current)).await
})
.await
{
Ok(result) => match result {
Ok(_) => {
let duration = task_start.elapsed().as_millis();
logging!(
info,
Type::Timer,
"Timer task completed successfully for uid: {} (took {}ms)",
uid,
duration
);
}
Err(e) => {
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
}
},
Err(_) => {
logging_error!(Type::Timer, false, "Timer task timed out for uid: {}", uid);
}
log::info!(target: "app", "running timer task `{uid}`");
// 使用更轻量级的更新方式
if let Err(e) = feat::update_profile(uid.clone(), None).await {
log::error!(target: "app", "timer task update error: {}", e);
return;
}
// Emit completed event
Self::emit_update_event(&uid, false);
// 只有更新成功后才刷新配置
if let Err(e) = CoreManager::global().update_config().await {
log::error!(target: "app", "timer task refresh error: {}", e);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,22 @@
use crate::{
module::mihomo::{MihomoManager, Rate},
utils::help::format_bytes_speed,
};
use crate::module::mihomo::Rate;
use crate::module::mihomo::MihomoManager;
use crate::utils::help::format_bytes_speed;
use ab_glyph::FontArc;
use anyhow::Result;
use futures::Stream;
use image::{GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut;
use parking_lot::Mutex;
use std::{io::Cursor, sync::Arc};
use std::io::Cursor;
use std::sync::Arc;
use tokio_tungstenite::tungstenite::http;
use tokio_tungstenite::tungstenite::Message;
use tungstenite::client::IntoClientRequest;
#[derive(Debug, Clone)]
pub struct SpeedRate {
rate: Arc<Mutex<(Rate, Rate)>>,
last_update: Arc<Mutex<std::time::Instant>>,
// 移除 base_image不再缓存原始图像
}
impl SpeedRate {
@@ -37,20 +39,12 @@ impl SpeedRate {
let (current, previous) = &mut *rates;
// Avoid unnecessary float conversions for small value checks
let should_update = if current.up < 1000 && down < 1000 {
// For small values, always update to ensure accuracy
current.up != up || current.down != down
} else {
// For larger values, use integer math to check for >5% change
// Multiply by 20 instead of dividing by 0.05 to avoid floating point
let up_threshold = current.up / 20;
let down_threshold = current.down / 20;
(up > current.up && up - current.up > up_threshold)
|| (up < current.up && current.up - up > up_threshold)
|| (down > current.down && down - current.down > down_threshold)
|| (down < current.down && current.down - down > down_threshold)
// 如果速率变化不大小于10%),则不更新
let should_update = {
let up_change = (current.up as f64 - up as f64).abs() / (current.up as f64 + 1.0);
let down_change =
(current.down as f64 - down as f64).abs() / (current.down as f64 + 1.0);
up_change > 0.1 || down_change > 0.1
};
if !should_update {
@@ -76,71 +70,44 @@ impl SpeedRate {
}
// 分离图标加载和速率渲染
pub fn add_speed_text(
is_custom_icon: bool,
icon_bytes: Option<Vec<u8>>,
rate: Option<&Rate>,
) -> Result<Vec<u8>> {
let rate = rate.unwrap_or(&Rate { up: 0, down: 0 });
pub fn add_speed_text(icon_bytes: Vec<u8>, rate: Option<Rate>) -> Result<Vec<u8>> {
let rate = rate.unwrap_or(Rate { up: 0, down: 0 });
let (mut icon_width, mut icon_height) = (0, 256);
let icon_image = if let Some(bytes) = icon_bytes.clone() {
let icon_image = image::load_from_memory(&bytes)?;
icon_width = icon_image.width();
icon_height = icon_image.height();
icon_image
} else {
// 返回一个空的 RGBA 图像
image::DynamicImage::new_rgba8(0, 0)
};
// 加载原始图标
let icon_image = image::load_from_memory(&icon_bytes)?;
let (icon_width, icon_height) = (icon_image.width(), icon_image.height());
let total_width = match (is_custom_icon, icon_bytes.is_some()) {
(true, true) => 510,
(true, false) => 740,
(false, false) => 740,
(false, true) => icon_width + 740,
};
// 判断是否为彩色图标
let is_colorful =
!crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
// println!(
// "icon_height: {}, icon_wight: {}, total_width: {}",
// icon_height, icon_width, total_width
// );
// 增加文本宽度和间距
let text_width = 580; // 文本区域宽度
let total_width = icon_width + text_width;
// 创建新的透明画布
let mut combined_image = RgbaImage::new(total_width, icon_height);
// 将原始图标绘制到新画布的左侧
if icon_bytes.is_some() {
for y in 0..icon_height {
for x in 0..icon_width {
let pixel = icon_image.get_pixel(x, y);
combined_image.put_pixel(x, y, pixel);
}
for y in 0..icon_height {
for x in 0..icon_width {
let pixel = icon_image.get_pixel(x, y);
combined_image.put_pixel(x, y, pixel);
}
}
let is_colorful = if let Some(bytes) = icon_bytes.clone() {
!crate::utils::help::is_monochrome_image_from_bytes(&bytes).unwrap_or(false)
} else {
false
};
// 选择文本颜色
let (text_color, shadow_color) = if is_colorful {
(
Rgba([144u8, 144u8, 144u8, 255u8]),
// Rgba([255u8, 255u8, 255u8, 128u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
)
// (
// Rgba([160u8, 160u8, 160u8, 255u8]),
// // Rgba([255u8, 255u8, 255u8, 128u8]),
// Rgba([0u8, 0u8, 0u8, 255u8]),
// )
} else {
// 彩色图标使用黑色文本和轻微白色阴影
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
Rgba([0u8, 0u8, 0u8, 160u8]),
)
} else {
// 单色图标使用白色文本和轻微黑色阴影
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 120u8]),
)
};
// 减小字体大小以适应文本区域
@@ -150,30 +117,17 @@ impl SpeedRate {
let scale = ab_glyph::PxScale::from(font_size);
// 使用更简洁的速率格式
let up_text = format!("{}", format_bytes_speed(rate.up));
let down_text = format!("{}", format_bytes_speed(rate.down));
// For test rate display
// let down_text = format!("↓ {}", format_bytes_speed(102 * 1020 * 1024));
let up_text = format_bytes_speed(rate.up);
let down_text = format_bytes_speed(rate.down);
// 计算文本位置,确保垂直间距合适
// 修改文本位置为居右显示
// 计算右对齐的文本位置
// let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
// let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
// let up_text_x = total_width - up_text_width;
// let down_text_x = total_width - down_text_width;
let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
// 计算对齐的文本位置
let (up_text_x, down_text_x) = {
if is_custom_icon || icon_bytes.is_some() {
let text_left_offset = 30;
let left_begin = icon_width + text_left_offset;
(left_begin, left_begin)
} else {
(icon_width, icon_width)
}
};
// 计算对齐的文本位置
let up_text_x = total_width - up_text_width;
let down_text_x = total_width - down_text_width;
// 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小
let text_height = font_size as i32;
@@ -240,92 +194,41 @@ pub struct Traffic {
impl Traffic {
pub async fn get_traffic_stream() -> Result<impl Stream<Item = Result<Traffic, anyhow::Error>>>
{
use futures::{
future::FutureExt,
stream::{self, StreamExt},
};
use futures::stream::{self, StreamExt};
use std::time::Duration;
// 先处理错误和超时情况
let stream = Box::pin(
stream::unfold((), move |_| async move {
'retry: loop {
log::info!(target: "app", "establishing traffic websocket connection");
stream::unfold((), |_| async {
loop {
let (url, token) = MihomoManager::get_traffic_ws_url();
let mut request = match url.into_client_request() {
Ok(req) => req,
Err(e) => {
log::error!(target: "app", "failed to create websocket request: {}", e);
tokio::time::sleep(Duration::from_secs(2)).await;
continue 'retry;
let mut request = url.into_client_request().unwrap();
request
.headers_mut()
.insert(http::header::AUTHORIZATION, token);
match tokio_tungstenite::connect_async(request).await {
Ok((ws_stream, _)) => {
log::info!(target: "app", "traffic ws connection established");
return Some((
ws_stream.map(|msg| {
msg.map_err(anyhow::Error::from).and_then(|msg: Message| {
let data = msg.into_text()?;
let json: serde_json::Value = serde_json::from_str(&data)?;
Ok(Traffic {
up: json["up"].as_u64().unwrap_or(0),
down: json["down"].as_u64().unwrap_or(0),
})
})
}),
(),
));
}
};
request.headers_mut().insert(http::header::AUTHORIZATION, token);
match tokio::time::timeout(Duration::from_secs(3),
tokio_tungstenite::connect_async(request)
).await {
Ok(Ok((ws_stream, _))) => {
log::info!(target: "app", "traffic websocket connection established");
// 设置流超时控制
let traffic_stream = ws_stream
.take_while(|msg| {
let continue_stream = msg.is_ok();
async move { continue_stream }.boxed()
})
.filter_map(|msg| async move {
match msg {
Ok(msg) => {
if !msg.is_text() {
return None;
}
match tokio::time::timeout(
Duration::from_millis(200),
async { msg.into_text() }
).await {
Ok(Ok(text)) => {
match serde_json::from_str::<serde_json::Value>(&text) {
Ok(json) => {
let up = json["up"].as_u64().unwrap_or(0);
let down = json["down"].as_u64().unwrap_or(0);
Some(Ok(Traffic { up, down }))
},
Err(e) => {
log::warn!(target: "app", "traffic json parse error: {} for {}", e, text);
None
}
}
},
Ok(Err(e)) => {
log::warn!(target: "app", "traffic text conversion error: {}", e);
None
},
Err(_) => {
log::warn!(target: "app", "traffic text processing timeout");
None
}
}
},
Err(e) => {
log::error!(target: "app", "traffic websocket error: {}", e);
None
}
}
});
return Some((traffic_stream, ()));
},
Ok(Err(e)) => {
log::error!(target: "app", "traffic websocket connection failed: {}", e);
},
Err(_) => {
log::error!(target: "app", "traffic websocket connection timed out");
Err(e) => {
log::error!(target: "app", "traffic ws connection failed: {e}");
tokio::time::sleep(Duration::from_secs(5)).await;
continue;
}
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
})
.flatten(),

View File

@@ -0,0 +1,14 @@
[package]
name = "mihomo_api"
edition = "2024"
[features]
debug = []
[dependencies]
reqwest = { version = "0.12.12", features = ["json"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.140"
[dev-dependencies]
tokio = { version = "1.43.0", features = ["rt", "macros"] }

View File

@@ -0,0 +1,134 @@
use reqwest::header::HeaderMap;
use serde_json::json;
use std::{
sync::{Arc, Mutex},
time::Duration,
};
pub mod model;
pub use model::{MihomoData, MihomoManager};
impl MihomoManager {
pub fn new(mihomo_server: String, headers: HeaderMap) -> Self {
Self {
mihomo_server,
data: Arc::new(Mutex::new(MihomoData {
proxies: serde_json::Value::Null,
providers_proxies: serde_json::Value::Null,
})),
headers: headers,
}
}
fn update_proxies(&self, proxies: serde_json::Value) {
let mut data = self.data.lock().unwrap();
data.proxies = proxies;
}
fn update_providers_proxies(&self, providers_proxies: serde_json::Value) {
let mut data = self.data.lock().unwrap();
data.providers_proxies = providers_proxies;
}
pub fn get_mihomo_server(&self) -> String {
self.mihomo_server.clone()
}
pub fn get_proxies(&self) -> serde_json::Value {
let data = self.data.lock().unwrap();
data.proxies.clone()
}
pub fn get_providers_proxies(&self) -> serde_json::Value {
let data = self.data.lock().unwrap();
data.providers_proxies.clone()
}
async fn send_request(
&self,
method: &str,
url: String,
data: Option<serde_json::Value>,
) -> Result<serde_json::Value, String> {
let client_response = reqwest::ClientBuilder::new()
.default_headers(self.headers.clone())
.no_proxy()
.timeout(Duration::from_secs(2))
.build()
.map_err(|e| e.to_string())?
.request(
match method {
"GET" => reqwest::Method::GET,
"PUT" => reqwest::Method::PUT,
"POST" => reqwest::Method::POST,
"PATCH" => reqwest::Method::PATCH,
_ => reqwest::Method::GET,
},
&url,
)
.json(&data.unwrap_or(json!({})))
.send()
.await
.map_err(|e| e.to_string())?;
let response = if method != "PUT" {
client_response.json::<serde_json::Value>().await
} else {
client_response.text().await.map(|text| json!(text))
}
.map_err(|e| e.to_string())?;
return Ok(response);
}
pub async fn refresh_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/proxies", self.mihomo_server);
let proxies = self.send_request("GET", url, None).await?;
self.update_proxies(proxies);
Ok(self)
}
pub async fn refresh_providers_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/providers/proxies", self.mihomo_server);
let providers_proxies = self.send_request("GET", url, None).await?;
self.update_providers_proxies(providers_proxies);
Ok(self)
}
}
impl MihomoManager {
pub async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), String> {
let url = format!("{}/configs?force=true", self.mihomo_server);
let payload = serde_json::json!({
"path": clash_config_path,
});
let _response = self.send_request("PUT", url, Some(payload)).await?;
Ok(())
}
pub async fn patch_configs(&self, config: serde_json::Value) -> Result<(), String> {
let url = format!("{}/configs", self.mihomo_server);
let response = self.send_request("PATCH", url, Some(config)).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string())
}
}
pub async fn test_proxy_delay(
&self,
name: &str,
test_url: Option<String>,
timeout: i32,
) -> Result<serde_json::Value, String> {
let test_url = test_url.unwrap_or("http://cp.cloudflare.com/generate_204".to_string());
let url = format!(
"{}/proxies/{}/delay?url={}&timeout={}",
self.mihomo_server, name, test_url, timeout
);
let response = self.send_request("GET", url, None).await?;
return Ok(response);
}
}

View File

@@ -0,0 +1,29 @@
use std::sync::{Arc, Mutex};
use reqwest::header::HeaderMap;
pub struct MihomoData {
pub(crate) proxies: serde_json::Value,
pub(crate) providers_proxies: serde_json::Value,
}
#[derive(Clone)]
pub struct MihomoManager {
pub(crate) mihomo_server: String,
pub(crate) data: Arc<Mutex<MihomoData>>,
pub(crate) headers: HeaderMap,
}
#[cfg(feature = "debug")]
impl Drop for MihomoData {
fn drop(&mut self) {
println!("Dropping MihomoData");
}
}
#[cfg(feature = "debug")]
impl Drop for MihomoManager {
fn drop(&mut self) {
println!("Dropping MihomoManager");
}
}

View File

@@ -0,0 +1,29 @@
use mihomo_api;
use reqwest::header::HeaderMap;
#[test]
fn test_mihomo_manager_init() {
let manager = mihomo_api::MihomoManager::new("url".into(), HeaderMap::new());
assert_eq!(manager.get_proxies(), serde_json::Value::Null);
assert_eq!(manager.get_providers_proxies(), serde_json::Value::Null);
}
#[tokio::test]
async fn test_refresh_proxies() {
let manager = mihomo_api::MihomoManager::new("http://127.0.0.1:9097".into(), HeaderMap::new());
let manager = manager.refresh_proxies().await.unwrap();
let proxies = manager.get_proxies();
let providers = manager.get_providers_proxies();
assert_ne!(proxies, serde_json::Value::Null);
assert_eq!(providers, serde_json::Value::Null);
}
#[tokio::test]
async fn test_refresh_providers_proxies() {
let manager = mihomo_api::MihomoManager::new("http://127.0.0.1:9097".into(), HeaderMap::new());
let manager = manager.refresh_providers_proxies().await.unwrap();
let proxies = manager.get_proxies();
let providers = manager.get_providers_proxies();
assert_eq!(proxies, serde_json::Value::Null);
assert_ne!(providers, serde_json::Value::Null);
}

View File

@@ -5,10 +5,17 @@ mod script;
pub mod seq;
mod tun;
use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
use crate::{config::Config, utils::tmpl};
use self::chain::*;
use self::field::*;
use self::merge::*;
use self::script::*;
use self::seq::*;
use self::tun::*;
use crate::config::Config;
use crate::utils::tmpl;
use serde_yaml::Mapping;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::collections::HashSet;
type ResultLog = Vec<(String, String)>;
@@ -22,7 +29,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
let verge = Config::verge();
let verge = verge.latest();
(
Some(verge.get_valid_clash_core()),
verge.clash_core.clone(),
verge.enable_tun_mode.unwrap_or(false),
verge.enable_builtin_enhanced.unwrap_or(true),
verge.verge_socks_enabled.unwrap_or(false),
@@ -260,30 +267,17 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if enable_dns_settings {
use crate::utils::dirs;
use std::fs;
// 尝试读取dns_config.yaml
if let Ok(app_dir) = dirs::app_home_dir() {
let dns_path = app_dir.join("dns_config.yaml");
if dns_path.exists() {
if let Ok(dns_yaml) = fs::read_to_string(&dns_path) {
if let Ok(dns_config) = serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {
// 处理hosts配置
if let Some(hosts_value) = dns_config.get("hosts") {
if hosts_value.is_mapping() {
config.insert("hosts".into(), hosts_value.clone());
log::info!(target: "app", "apply hosts configuration");
}
}
if let Some(dns_value) = dns_config.get("dns") {
if let Some(dns_mapping) = dns_value.as_mapping() {
config.insert("dns".into(), dns_mapping.clone().into());
log::info!(target: "app", "apply dns_config.yaml (dns section)");
}
} else {
config.insert("dns".into(), dns_config.into());
log::info!(target: "app", "apply dns_config.yaml");
}
// 将DNS配置合并到最终配置
config.insert("dns".into(), dns_config.into());
log::info!(target: "app", "apply dns_config.yaml");
}
}
}

View File

@@ -33,40 +33,37 @@ pub fn use_script(
}
let _ = context.eval(Source::from_bytes(
r#"var console = Object.freeze({
log(data){__verge_log__("log",JSON.stringify(data, null, 2))},
info(data){__verge_log__("info",JSON.stringify(data, null, 2))},
error(data){__verge_log__("error",JSON.stringify(data, null, 2))},
debug(data){__verge_log__("debug",JSON.stringify(data, null, 2))},
warn(data){__verge_log__("warn",JSON.stringify(data, null, 2))},
table(data){__verge_log__("table",JSON.stringify(data, null, 2))},
log(data){__verge_log__("log",JSON.stringify(data))},
info(data){__verge_log__("info",JSON.stringify(data))},
error(data){__verge_log__("error",JSON.stringify(data))},
debug(data){__verge_log__("debug",JSON.stringify(data))},
});"#,
));
let config = use_lowercase(config.clone());
let config_str = serde_json::to_string(&config)?;
// 仅处理 name 参数中的特殊字符
let safe_name = escape_js_string_for_single_quote(&name);
let code = format!(
r#"try{{
{script};
JSON.stringify(main({config_str},'{safe_name}')||'')
JSON.stringify(main({config_str},'{name}')||'')
}} catch(err) {{
`__error_flag__ ${{err.toString()}}`
}}"#
);
if let Ok(result) = context.eval(Source::from_bytes(code.as_str())) {
if !result.is_string() {
anyhow::bail!("main function should return object");
}
let result = result.to_string(&mut context).unwrap();
let result = result.to_std_string().unwrap();
// 直接解析JSON结果,不做其他解析
let res: Result<Mapping, Error> = parse_json_safely(&result);
if result.starts_with("__error_flag__") {
anyhow::bail!(result[15..].to_owned());
}
if result == "\"\"" {
anyhow::bail!("main function should return object");
}
let res: Result<Mapping, Error> = Ok(serde_json::from_str::<Mapping>(result.as_str())?);
let mut out = outputs.lock().unwrap();
match res {
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
@@ -80,27 +77,6 @@ pub fn use_script(
}
}
fn parse_json_safely(json_str: &str) -> Result<Mapping, Error> {
let json_str = strip_outer_quotes(json_str);
Ok(serde_json::from_str::<Mapping>(json_str)?)
}
// 移除字符串外层的引号
fn strip_outer_quotes(s: &str) -> &str {
let s = s.trim();
if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
&s[1..s.len() - 1]
} else {
s
}
}
// 转义单引号和反斜杠用于单引号包裹的JavaScript字符串
fn escape_js_string_for_single_quote(s: &str) -> String {
s.replace('\\', "\\\\").replace('\'', "\\'")
}
#[test]
fn test_script() {
let script = r#"
@@ -128,31 +104,6 @@ fn test_script() {
let (config, results) = use_script(script.into(), config, "".to_string()).unwrap();
let _ = serde_yaml::to_string(&config).unwrap();
let yaml_config_size = std::mem::size_of_val(&config);
dbg!(yaml_config_size);
let box_yaml_config_size = std::mem::size_of_val(&Box::new(config));
dbg!(box_yaml_config_size);
dbg!(results);
assert!(box_yaml_config_size < yaml_config_size);
}
// 测试特殊字符转义功能
#[test]
fn test_escape_unescape() {
let test_string = r#"Hello "World"!\nThis is a test with \u00A9 copyright symbol."#;
let escaped = escape_js_string_for_single_quote(test_string);
println!("Original: {}", test_string);
println!("Escaped: {}", escaped);
let json_str = r#"{"key":"value","nested":{"key":"value"}}"#;
let parsed = parse_json_safely(json_str).unwrap();
assert!(parsed.contains_key("key"));
assert!(parsed.contains_key("nested"));
let quoted_json_str = r#""{"key":"value","nested":{"key":"value"}}""#;
let parsed_quoted = parse_json_safely(quoted_json_str).unwrap();
assert!(parsed_quoted.contains_key("key"));
assert!(parsed_quoted.contains_key("nested"));
}

View File

@@ -108,7 +108,7 @@ proxy-groups:
- "proxy1"
"#;
let mut config: Mapping = serde_yaml::from_str(config_str).unwrap();
let seq = SeqMap {
prepend: Sequence::new(),
append: Sequence::new(),
@@ -121,32 +121,16 @@ proxy-groups:
let proxies = config.get("proxies").unwrap().as_sequence().unwrap();
assert_eq!(proxies.len(), 1);
assert_eq!(
proxies[0]
.as_mapping()
.unwrap()
.get("name")
.unwrap()
.as_str()
.unwrap(),
proxies[0].as_mapping().unwrap().get("name").unwrap().as_str().unwrap(),
"proxy2"
);
// Check if proxy1 is removed from all groups
let groups = config.get("proxy-groups").unwrap().as_sequence().unwrap();
let group1_proxies = groups[0]
.as_mapping()
.unwrap()
.get("proxies")
.unwrap()
.as_sequence()
.unwrap();
let group2_proxies = groups[1]
.as_mapping()
.unwrap()
.get("proxies")
.unwrap()
.as_sequence()
.unwrap();
let group1_proxies = groups[0].as_mapping().unwrap()
.get("proxies").unwrap().as_sequence().unwrap();
let group2_proxies = groups[1].as_mapping().unwrap()
.get("proxies").unwrap().as_sequence().unwrap();
assert_eq!(group1_proxies.len(), 1);
assert_eq!(group1_proxies[0].as_str().unwrap(), "proxy2");

View File

@@ -24,7 +24,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
let mut tun_val = tun_val.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
if enable {
// 读取DNS配置
let dns_key = Value::from("dns");
@@ -40,20 +40,20 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 检查现有的 enhanced-mode 设置
let current_mode = dns_val
.get(Value::from("enhanced-mode"))
.get(&Value::from("enhanced-mode"))
.and_then(|v| v.as_str())
.unwrap_or("fake-ip");
// 只有当 enhanced-mode 是 fake-ip 或未设置时才修改 DNS 配置
if current_mode == "fake-ip" || !dns_val.contains_key(Value::from("enhanced-mode")) {
if current_mode == "fake-ip" || !dns_val.contains_key(&Value::from("enhanced-mode")) {
revise!(dns_val, "enable", true);
revise!(dns_val, "ipv6", ipv6_val);
if !dns_val.contains_key(Value::from("enhanced-mode")) {
if !dns_val.contains_key(&Value::from("enhanced-mode")) {
revise!(dns_val, "enhanced-mode", "fake-ip");
}
if !dns_val.contains_key(Value::from("fake-ip-range")) {
if !dns_val.contains_key(&Value::from("fake-ip-range")) {
revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
}
@@ -63,7 +63,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
}
}
// 当TUN启用时将修改后的DNS配置写回
revise!(config, "dns", dns_val);
} else {
@@ -75,6 +75,6 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 更新TUN配置
revise!(tun_val, "enable", enable);
revise!(config, "tun", tun_val);
config
}

View File

@@ -1,9 +1,7 @@
use crate::{
config::{Config, IVerge},
core::backup,
logging_error,
utils::{dirs::app_home_dir, logging::Type},
};
use crate::config::{Config, IVerge};
use crate::core::backup;
use crate::log_err;
use crate::utils::dirs::app_home_dir;
use anyhow::Result;
use reqwest_dav::list_cmd::ListFile;
use std::fs;
@@ -69,9 +67,8 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
// extract zip file
let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?;
zip.extract(app_home_dir()?)?;
logging_error!(
Type::Backup,
true,
log_err!(
super::patch_verge(
IVerge {
webdav_url,

View File

@@ -1,17 +1,14 @@
use crate::{
config::Config,
core::{handle, tray, CoreManager},
logging_error,
module::mihomo::MihomoManager,
process::AsyncHandler,
utils::{logging::Type, resolve},
};
use crate::config::Config;
use crate::core::{handle, tray, CoreManager};
use crate::log_err;
use crate::module::mihomo::MihomoManager;
use crate::utils::resolve;
use serde_yaml::{Mapping, Value};
use tauri::Manager;
/// Restart the Clash core
pub fn restart_clash_core() {
AsyncHandler::spawn(move || async move {
tauri::async_runtime::spawn(async {
match CoreManager::global().restart_core().await {
Ok(_) => {
handle::Handle::refresh_clash();
@@ -27,34 +24,17 @@ pub fn restart_clash_core() {
/// Restart the application
pub fn restart_app() {
AsyncHandler::spawn(move || async move {
logging_error!(Type::Core, true, CoreManager::global().stop_core().await);
resolve::resolve_reset_async().await;
tauri::async_runtime::spawn_blocking(|| {
tauri::async_runtime::block_on(async {
log_err!(CoreManager::global().stop_core().await);
});
resolve::resolve_reset();
let app_handle = handle::Handle::global().app_handle().unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
tauri::process::restart(&app_handle.env());
});
}
fn after_change_clash_mode() {
AsyncHandler::spawn(move || async {
match MihomoManager::global().get_connections().await {
Ok(connections) => {
if let Some(connections_array) = connections["connections"].as_array() {
for connection in connections_array {
if let Some(id) = connection["id"].as_str() {
let _ = MihomoManager::global().delete_connection(id).await;
}
}
}
}
Err(err) => {
log::error!(target: "app", "Failed to get connections: {}", err);
}
}
});
}
/// Change Clash mode (rule/global/direct/script)
pub fn change_clash_mode(mode: String) {
let mut mapping = Mapping::new();
@@ -63,8 +43,9 @@ pub fn change_clash_mode(mode: String) {
let json_value = serde_json::json!({
"mode": mode
});
AsyncHandler::spawn(move || async move {
tauri::async_runtime::spawn(async move {
log::debug!(target: "app", "change clash mode to {mode}");
match MihomoManager::global().patch_configs(json_value).await {
Ok(_) => {
// 更新订阅
@@ -72,16 +53,8 @@ pub fn change_clash_mode(mode: String) {
if Config::clash().data().save_config().is_ok() {
handle::Handle::refresh_clash();
logging_error!(Type::Tray, true, tray::Tray::global().update_menu());
logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None));
}
let is_auto_close_connection = Config::verge()
.data()
.auto_close_connection
.unwrap_or(false);
if is_auto_close_connection {
after_change_clash_mode();
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
}
}
Err(err) => log::error!(target: "app", "{err}"),
@@ -91,26 +64,36 @@ pub fn change_clash_mode(mode: String) {
/// Test connection delay to a URL
pub async fn test_delay(url: String) -> anyhow::Result<u32> {
use crate::utils::network::{NetworkManager, ProxyType};
use tokio::time::Instant;
use tokio::time::{Duration, Instant};
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false);
// 如果是TUN模式不使用代理否则使用自身代理
let proxy_type = if !tun_mode {
ProxyType::Localhost
} else {
ProxyType::None
};
let proxy_scheme = format!("http://127.0.0.1:{port}");
let user_agent = Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0".to_string());
if !tun_mode {
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
let request = builder
.timeout(Duration::from_millis(10000))
.build()?
.get(url).header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");
let start = Instant::now();
let response = NetworkManager::global()
.get_with_interrupt(&url, proxy_type, Some(10), user_agent, false)
.await;
let response = request.send().await;
match response {
Ok(response) => {
log::trace!(target: "app", "test_delay response: {:#?}", response);
@@ -122,7 +105,7 @@ pub async fn test_delay(url: String) -> anyhow::Result<u32> {
}
Err(err) => {
log::trace!(target: "app", "test_delay error: {:#?}", err);
Err(err)
Err(err.into())
}
}
}

View File

@@ -1,12 +1,10 @@
use crate::{
config::{Config, IVerge},
core::{handle, hotkey, sysopt, tray, CoreManager},
logging_error,
module::lightweight,
utils::logging::Type,
};
use crate::config::{Config, IVerge};
use crate::core::{handle, hotkey, sysopt, tray, CoreManager};
use crate::log_err;
use crate::utils::resolve;
use anyhow::Result;
use serde_yaml::Mapping;
use tauri::Manager;
/// Patch Clash configuration
pub async fn patch_clash(patch: Mapping) -> Result<()> {
@@ -19,8 +17,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
CoreManager::global().restart_core().await?;
} else {
if patch.get("mode").is_some() {
logging_error!(Type::Tray, true, tray::Tray::global().update_menu());
logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None));
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
}
Config::runtime().latest().patch_config(patch);
CoreManager::global().update_config().await?;
@@ -41,23 +39,6 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
}
}
// Define update flags as bitflags for better performance
#[derive(Clone, Copy)]
enum UpdateFlags {
None = 0,
RestartCore = 1 << 0,
ClashConfig = 1 << 1,
VergeConfig = 1 << 2,
Launch = 1 << 3,
SysProxy = 1 << 4,
SystrayIcon = 1 << 5,
Hotkey = 1 << 6,
SystrayMenu = 1 << 7,
SystrayTooltip = 1 << 8,
SystrayClickBehavior = 1 << 9,
LighteWeight = 1 << 10,
}
/// Patch Verge configuration
pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
Config::verge().draft().patch_config(patch.clone());
@@ -70,6 +51,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let proxy_bypass = patch.system_proxy_bypass;
let language = patch.language;
let mixed_port = patch.verge_mixed_port;
let lite_mode = patch.enable_lite_mode;
#[cfg(target_os = "macos")]
let tray_icon = patch.tray_icon;
#[cfg(not(target_os = "macos"))]
@@ -90,31 +72,35 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let http_enabled = patch.verge_http_enabled;
let http_port = patch.verge_port;
let enable_tray_speed = patch.enable_tray_speed;
let enable_tray_icon = patch.enable_tray_icon;
let enable_global_hotkey = patch.enable_global_hotkey;
let tray_event = patch.tray_event;
let home_cards = patch.home_cards.clone();
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
let res: std::result::Result<(), anyhow::Error> = {
// Initialize with no flags set
let mut update_flags: i32 = UpdateFlags::None as i32;
let mut should_restart_core = false;
let mut should_update_clash_config = false;
let mut should_update_verge_config = false;
let mut should_update_launch = false;
let mut should_update_sysproxy = false;
let mut should_update_systray_icon = false;
let mut should_update_hotkey = false;
let mut should_update_systray_menu = false;
let mut should_update_systray_tooltip = false;
if tun_mode.is_some() {
update_flags |= UpdateFlags::ClashConfig as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
should_update_clash_config = true;
should_update_systray_menu = true;
should_update_systray_tooltip = true;
should_update_systray_icon = true;
}
if enable_global_hotkey.is_some() || home_cards.is_some() {
update_flags |= UpdateFlags::VergeConfig as i32;
if enable_global_hotkey.is_some() {
should_update_verge_config = true;
}
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
should_restart_core = true;
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
should_restart_core = true;
}
if socks_enabled.is_some()
|| http_enabled.is_some()
@@ -122,88 +108,91 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|| http_port.is_some()
|| mixed_port.is_some()
{
update_flags |= UpdateFlags::RestartCore as i32;
should_restart_core = true;
}
if auto_launch.is_some() {
update_flags |= UpdateFlags::Launch as i32;
should_update_launch = true;
}
if system_proxy.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
should_update_sysproxy = true;
should_update_systray_menu = true;
should_update_systray_tooltip = true;
should_update_systray_icon = true;
}
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
should_update_sysproxy = true;
}
if language.is_some() {
update_flags |= UpdateFlags::SystrayMenu as i32;
should_update_systray_menu = true;
}
if common_tray_icon.is_some()
|| sysproxy_tray_icon.is_some()
|| tun_tray_icon.is_some()
|| tray_icon.is_some()
|| enable_tray_speed.is_some()
|| enable_tray_icon.is_some()
{
update_flags |= UpdateFlags::SystrayIcon as i32;
should_update_systray_icon = true;
}
if patch.hotkeys.is_some() {
update_flags |= UpdateFlags::Hotkey as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
should_update_hotkey = true;
should_update_systray_menu = true;
}
if tray_event.is_some() {
update_flags |= UpdateFlags::SystrayClickBehavior as i32;
if enable_tray_speed.is_some() {
should_update_systray_icon = true;
}
if enable_auto_light_weight.is_some() {
update_flags |= UpdateFlags::LighteWeight as i32;
}
// Process updates based on flags
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
Config::generate().await?;
if should_restart_core {
CoreManager::global().restart_core().await?;
}
if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 {
if should_update_clash_config {
CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
}
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
if should_update_verge_config {
Config::verge().draft().enable_global_hotkey = enable_global_hotkey;
handle::Handle::refresh_verge();
}
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
if should_update_launch {
sysopt::Sysopt::global().update_launch()?;
}
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
if should_update_sysproxy {
sysopt::Sysopt::global().update_sysproxy().await?;
}
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0 {
if should_update_hotkey {
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
}
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
if should_update_systray_menu {
tray::Tray::global().update_menu()?;
}
if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 {
if should_update_systray_icon {
tray::Tray::global().update_icon(None)?;
}
if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 {
if should_update_systray_tooltip {
tray::Tray::global().update_tooltip()?;
}
if (update_flags & (UpdateFlags::SystrayClickBehavior as i32)) != 0 {
tray::Tray::global().update_click_behavior()?;
}
if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 {
if enable_auto_light_weight.unwrap() {
lightweight::enable_auto_light_weight_mode();
} else {
lightweight::disable_auto_light_weight_mode();
// 处理轻量模式切换
if lite_mode.is_some() {
if let Some(window) = handle::Handle::global().get_window() {
if lite_mode.unwrap() {
// 完全退出 webview 进程
window.close()?; // 先关闭窗口
let app_handle = handle::Handle::global().app_handle().unwrap();
if let Some(webview) = app_handle.get_webview_window("main") {
webview.destroy()?; // 销毁 webview 进程
}
} else {
resolve::create_window(); // 重新创建窗口
}
}
}

View File

@@ -1,16 +1,13 @@
use crate::{
cmd,
config::{Config, PrfItem, PrfOption},
core::{handle, CoreManager, *},
logging,
process::AsyncHandler,
utils::logging::Type,
};
use crate::cmd;
use crate::config::{Config, PrfItem, PrfOption};
use crate::core::handle;
use crate::core::CoreManager;
use crate::core::*;
use anyhow::{bail, Result};
/// Toggle proxy profile
pub fn toggle_proxy_profile(profile_index: String) {
AsyncHandler::spawn(|| async move {
tauri::async_runtime::spawn(async move {
let app_handle = handle::Handle::global().app_handle().unwrap();
match cmd::patch_profiles_config_by_profile_index(app_handle, profile_index).await {
Ok(_) => {
@@ -25,29 +22,23 @@ pub fn toggle_proxy_profile(profile_index: String) {
/// Update a profile
/// If updating current profile, activate it
/// auto_refresh: 是否自动更新配置和刷新前端
pub async fn update_profile(
uid: String,
option: Option<PrfOption>,
auto_refresh: Option<bool>,
) -> Result<()> {
logging!(info, Type::Config, true, "[订阅更新] 开始更新订阅 {}", uid);
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true保持兼容性
pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
println!("[订阅更新] 开始更新订阅 {}", uid);
let url_opt = {
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
if !is_remote {
log::info!(target: "app", "[订阅更新] {} 不是远程订阅,跳过更新", uid);
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);
None // 非远程订阅直接更新
} else if item.url.is_none() {
log::warn!(target: "app", "[订阅更新] {} 缺少URL无法更新", uid);
println!("[订阅更新] {} 缺少URL无法更新", uid);
bail!("failed to get the profile item url");
} else {
log::info!(target: "app",
println!(
"[订阅更新] {} 是远程订阅URL: {}",
uid,
item.url.clone().unwrap()
@@ -58,88 +49,32 @@ pub async fn update_profile(
let should_update = match url_opt {
Some((url, opt)) => {
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
println!("[订阅更新] 开始下载新的订阅内容");
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
// 尝试使用正常设置更新
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
Ok(item) => {
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
let profiles = Config::profiles();
let mut profiles = profiles.latest();
profiles.update_item(uid.clone(), item)?;
println!("[订阅更新] 更新订阅配置");
let profiles = Config::profiles();
let mut profiles = profiles.latest();
profiles.update_item(uid.clone(), item)?;
let is_current = Some(uid.clone()) == profiles.get_current();
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current && auto_refresh
}
Err(err) => {
// 首次更新失败尝试使用Clash代理
log::warn!(target: "app", "[订阅更新] 正常更新失败: {}尝试使用Clash代理更新", err);
// 发送通知
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
// 保存原始代理设置
let original_with_proxy = merged_opt.as_ref().and_then(|o| o.with_proxy);
let original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy);
// 创建使用Clash代理的选项
let mut fallback_opt = merged_opt.unwrap_or_default();
fallback_opt.with_proxy = Some(false);
fallback_opt.self_proxy = Some(true);
// 使用Clash代理重试
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
Ok(mut item) => {
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
// 恢复原始代理设置到item
if let Some(option) = item.option.as_mut() {
option.with_proxy = original_with_proxy;
option.self_proxy = original_self_proxy;
}
// 更新到配置
let profiles = Config::profiles();
let mut profiles = profiles.latest();
profiles.update_item(uid.clone(), item.clone())?;
// 获取配置名称用于通知
let profile_name = item.name.clone().unwrap_or_else(|| uid.clone());
// 发送通知告知用户自动更新使用了回退机制
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
let is_current = Some(uid.clone()) == profiles.get_current();
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current && auto_refresh
}
Err(retry_err) => {
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {}", retry_err);
handle::Handle::notice_message(
"update_failed_even_with_clash",
format!("{}", retry_err),
);
return Err(retry_err);
}
}
}
}
let is_current = Some(uid.clone()) == profiles.get_current();
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current
}
None => auto_refresh,
None => true,
};
if should_update {
logging!(info, Type::Config, true, "[订阅更新] 更新内核配置");
println!("[订阅更新] 更新内核配置");
match CoreManager::global().update_config().await {
Ok(_) => {
logging!(info, Type::Config, true, "[订阅更新] 更新成功");
println!("[订阅更新] 更新成功");
handle::Handle::refresh_clash();
}
Err(err) => {
logging!(error, Type::Config, true, "[订阅更新] 更新失败: {}", err);
handle::Handle::notice_message("update_failed", format!("{err}"));
println!("[订阅更新] 更新失败: {}", err);
handle::Handle::notice_message("set_config::error", format!("{err}"));
log::error!(target: "app", "{err}");
}
}
@@ -147,11 +82,3 @@ pub async fn update_profile(
Ok(())
}
/// 增强配置
pub async fn enhance_profiles() -> Result<()> {
crate::core::CoreManager::global()
.update_config()
.await
.map(|_| ())
}

View File

@@ -1,8 +1,6 @@
use crate::{
config::{Config, IVerge},
core::handle,
process::AsyncHandler,
};
use crate::config::Config;
use crate::config::IVerge;
use crate::core::handle;
use std::env;
use tauri_plugin_clipboard_manager::ClipboardExt;
@@ -10,22 +8,8 @@ use tauri_plugin_clipboard_manager::ClipboardExt;
pub fn toggle_system_proxy() {
let enable = Config::verge().draft().enable_system_proxy;
let enable = enable.unwrap_or(false);
let auto_close_connection = Config::verge()
.data()
.auto_close_connection
.unwrap_or(false);
AsyncHandler::spawn(move || async move {
// 如果当前系统代理即将关闭且自动关闭连接设置为true则关闭所有连接
if enable && auto_close_connection {
if let Err(err) = crate::module::mihomo::MihomoManager::global()
.close_all_connections()
.await
{
log::error!(target: "app", "Failed to close all connections: {}", err);
}
}
tauri::async_runtime::spawn(async move {
match super::patch_verge(
IVerge {
enable_system_proxy: Some(!enable),
@@ -46,7 +30,7 @@ pub fn toggle_tun_mode(not_save_file: Option<bool>) {
let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false);
AsyncHandler::spawn(async move || {
tauri::async_runtime::spawn(async move {
match super::patch_verge(
IVerge {
enable_tun_mode: Some(!enable),
@@ -64,14 +48,9 @@ pub fn toggle_tun_mode(not_save_file: Option<bool>) {
/// Copy proxy environment variables to clipboard
pub fn copy_clash_env() {
// 从环境变量获取IP地址如果没有则从配置中获取 proxy_host默认为 127.0.0.1
let clash_verge_rev_ip = env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| {
Config::verge()
.latest()
.proxy_host
.clone()
.unwrap_or_else(|| "127.0.0.1".to_string())
});
// 从环境变量获取IP地址默认127.0.0.1
let clash_verge_rev_ip =
env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| "127.0.0.1".to_string());
let app_handle = handle::Handle::global().app_handle().unwrap();
let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) };
@@ -93,16 +72,10 @@ pub fn copy_clash_env() {
};
let export_text = match env_type.as_str() {
"bash" => format!(
"export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"
),
"bash" => format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"),
"cmd" => format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}"),
"powershell" => {
format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"")
}
"nushell" => {
format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}")
}
"powershell" => format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\""),
"nushell" => format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}"),
"fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"),
_ => {
log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}");
@@ -110,7 +83,7 @@ pub fn copy_clash_env() {
}
};
if cliboard.write_text(export_text).is_err() {
if let Err(_) = cliboard.write_text(export_text) {
log::error!(target: "app", "Failed to write to clipboard");
}
}

View File

@@ -1,39 +1,87 @@
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::Config,
core::{handle, sysopt, CoreManager},
logging,
module::mihomo::MihomoManager,
utils::logging::Type,
};
use crate::config::Config;
use crate::core::handle;
use crate::core::{sysopt, CoreManager};
use crate::module::mihomo::MihomoManager;
use crate::utils::resolve;
use futures;
use tauri::Manager;
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
/// Open or close the dashboard window
#[allow(dead_code)]
pub fn open_or_close_dashboard() {
use crate::utils::window_manager::WindowManager;
println!("Attempting to open/close dashboard");
log::info!(target: "app", "Attempting to open/close dashboard");
// 检查是否在轻量模式下
if crate::module::lightweight::is_in_lightweight_mode() {
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
crate::module::lightweight::exit_lightweight_mode();
log::info!(target: "app", "Creating new window after exiting lightweight mode");
let result = WindowManager::show_main_window();
log::info!(target: "app", "Window operation result: {:?}", result);
return;
}
if let Some(window) = handle::Handle::global().get_window() {
println!("Found existing window");
log::info!(target: "app", "Found existing window");
// 使用统一的窗口管理器切换窗口状态
let result = WindowManager::toggle_main_window();
log::info!(target: "app", "Window toggle result: {:?}", result);
// 如果窗口存在,则切换其显示状态
match window.is_visible() {
Ok(visible) => {
println!("Window visibility status: {}", visible);
log::info!(target: "app", "Window visibility status: {}", visible);
if visible {
println!("Attempting to hide window");
log::info!(target: "app", "Attempting to hide window");
let _ = window.hide();
} else {
println!("Attempting to show and focus window");
log::info!(target: "app", "Attempting to show and focus window");
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
let _ = window.show();
let _ = window.set_focus();
}
}
Err(e) => {
println!("Failed to get window visibility: {:?}", e);
log::error!(target: "app", "Failed to get window visibility: {:?}", e);
}
}
} else {
println!("No existing window found, creating new window");
log::info!(target: "app", "No existing window found, creating new window");
resolve::create_window();
}
}
/// 异步优化的应用退出函数
pub fn quit() {
use crate::process::AsyncHandler;
logging!(debug, Type::System, true, "启动退出流程");
/// Setup window state monitor to save window position and size in real-time
pub fn setup_window_state_monitor(app_handle: &tauri::AppHandle) {
let window = app_handle.get_webview_window("main").unwrap();
let app_handle_clone = app_handle.clone();
// 监听窗口移动事件
let app_handle_move = app_handle_clone.clone();
window.on_window_event(move |event| {
match event {
// 窗口移动时保存状态
tauri::WindowEvent::Moved(_) => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
// 窗口调整大小时保存状态
tauri::WindowEvent::Resized(_) => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
// 其他可能改变窗口状态的事件
tauri::WindowEvent::ScaleFactorChanged { .. } => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
// 窗口关闭时保存
tauri::WindowEvent::CloseRequested { .. } => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
_ => {}
}
});
}
/// 优化的应用退出函数
pub fn quit(code: Option<i32>) {
log::debug!(target: "app", "启动退出流程");
// 获取应用句柄并设置退出标志
let app_handle = handle::Handle::global().app_handle().unwrap();
@@ -42,185 +90,50 @@ pub fn quit() {
// 优先关闭窗口,提供立即反馈
if let Some(window) = handle::Handle::global().get_window() {
let _ = window.hide();
log::info!(target: "app", "窗口已隐藏");
}
// 使用异步任务处理资源清理,避免阻塞
AsyncHandler::spawn(move || async move {
logging!(info, Type::System, true, "开始异步清理资源");
let cleanup_result = clean_async().await;
// 在单独线程中处理资源清理,避免阻塞主线程
std::thread::spawn(move || {
// 使用tokio运行时执行异步清理任务
tauri::async_runtime::block_on(async {
// 使用超时机制处理清理操作
use tokio::time::{timeout, Duration};
logging!(
info,
Type::System,
true,
"资源清理完成,退出代码: {}",
if cleanup_result { 0 } else { 1 }
);
app_handle.exit(if cleanup_result { 0 } else { 1 });
});
}
// 1. 直接关闭TUN模式 (优先处理,通常最容易卡住)
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
let disable = serde_json::json!({
"tun": {
"enable": false
}
});
async fn clean_async() -> bool {
use tokio::time::{timeout, Duration};
logging!(info, Type::System, true, "开始执行异步清理操作...");
// 1. 处理TUN模式
let tun_task = async {
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
let disable_tun = serde_json::json!({
"tun": {
"enable": false
}
});
match timeout(
Duration::from_secs(2),
MihomoManager::global().patch_configs(disable_tun),
)
.await
{
Ok(_) => {
log::info!(target: "app", "TUN模式已禁用");
true
}
Err(_) => {
log::warn!(target: "app", "禁用TUN模式超时");
false
}
// 设置1秒超时
let _ = timeout(
Duration::from_secs(1),
MihomoManager::global().patch_configs(disable),
)
.await;
}
} else {
true
}
};
// 2. 系统代理重置
let proxy_task = async {
match timeout(
Duration::from_secs(3),
sysopt::Sysopt::global().reset_sysproxy(),
)
.await
{
Ok(_) => {
log::info!(target: "app", "系统代理已重置");
true
}
Err(_) => {
log::warn!(target: "app", "重置系统代理超时");
false
}
}
};
// 3. 核心服务停止
let core_task = async {
match timeout(Duration::from_secs(3), CoreManager::global().stop_core()).await {
Ok(_) => {
log::info!(target: "app", "核心服务已停止");
true
}
Err(_) => {
log::warn!(target: "app", "停止核心服务超时");
false
}
}
};
// 4. DNS恢复仅macOS
#[cfg(target_os = "macos")]
let dns_task = async {
match timeout(
Duration::from_millis(1000),
crate::utils::resolve::restore_public_dns(),
)
.await
{
Ok(_) => {
log::info!(target: "app", "DNS设置已恢复");
true
}
Err(_) => {
log::warn!(target: "app", "恢复DNS设置超时");
false
}
}
};
// 并行执行所有清理任务
let (tun_success, proxy_success, core_success) = tokio::join!(tun_task, proxy_task, core_task);
#[cfg(target_os = "macos")]
let dns_success = dns_task.await;
#[cfg(not(target_os = "macos"))]
let dns_success = true;
let all_success = tun_success && proxy_success && core_success && dns_success;
logging!(
info,
Type::System,
true,
"异步清理操作完成 - TUN: {}, 代理: {}, 核心: {}, DNS: {}, 总体: {}",
tun_success,
proxy_success,
core_success,
dns_success,
all_success
);
all_success
}
pub fn clean() -> bool {
use crate::process::AsyncHandler;
let (tx, rx) = std::sync::mpsc::channel();
AsyncHandler::spawn(move || async move {
logging!(info, Type::System, true, "开始执行清理操作...");
// 使用已有的异步清理函数
let cleanup_result = clean_async().await;
// 发送结果
let _ = tx.send(cleanup_result);
});
match rx.recv_timeout(std::time::Duration::from_secs(8)) {
Ok(result) => {
logging!(info, Type::System, true, "清理操作完成,结果: {}", result);
result
}
Err(_) => {
logging!(
warn,
Type::System,
true,
"清理操作超时,返回成功状态避免阻塞"
// 2. 并行处理系统代理和核心进程清理
let proxy_future = timeout(
Duration::from_secs(1),
sysopt::Sysopt::global().reset_sysproxy(),
);
true
}
}
}
#[cfg(target_os = "macos")]
pub fn hide() {
use crate::module::lightweight::add_light_weight_timer;
let enable_auto_light_weight_mode = Config::verge()
.data()
.enable_auto_light_weight_mode
.unwrap_or(false);
if enable_auto_light_weight_mode {
add_light_weight_timer();
}
if let Some(window) = handle::Handle::global().get_window() {
if window.is_visible().unwrap_or(false) {
let _ = window.hide();
}
}
AppHandleManager::global().set_activation_policy_accessory();
let core_future = timeout(Duration::from_secs(1), CoreManager::global().stop_core());
// 同时等待两个任务完成
let _ = futures::join!(proxy_future, core_future);
// 3. 处理macOS特定清理
#[cfg(target_os = "macos")]
{
let _ = timeout(Duration::from_millis(500), resolve::restore_public_dns()).await;
}
});
// 无论清理结果如何,确保应用退出
app_handle.exit(code.unwrap_or(0));
});
}

View File

@@ -3,24 +3,17 @@ mod config;
mod core;
mod enhance;
mod feat;
mod module;
mod process;
mod state;
mod utils;
use crate::{
core::hotkey,
process::AsyncHandler,
utils::{resolve, resolve::resolve_scheme, server},
};
mod module;
use crate::core::hotkey;
use crate::utils::{resolve, resolve::resolve_scheme, server};
use config::Config;
use std::sync::{Mutex, Once};
use tauri::AppHandle;
use tauri::Manager;
#[cfg(target_os = "macos")]
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use tokio::time::{timeout, Duration};
use utils::logging::Type;
use std::sync::{Mutex, Once};
use tauri::AppHandle;
#[cfg(target_os = "macos")]
use tauri::Manager;
/// A global singleton handle to the application.
pub struct AppHandleManager {
@@ -64,7 +57,7 @@ impl AppHandleManager {
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular);
}
}
pub fn set_activation_policy_accessory(&self) {
#[cfg(target_os = "macos")]
{
@@ -73,7 +66,7 @@ impl AppHandleManager {
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory);
}
}
pub fn set_activation_policy_prohibited(&self) {
#[cfg(target_os = "macos")]
{
@@ -84,38 +77,19 @@ impl AppHandleManager {
}
}
#[allow(clippy::panic)]
pub fn run() {
utils::network::NetworkManager::global().init();
let _ = utils::dirs::init_portable_flag();
// 异步单例检测
AsyncHandler::spawn(move || async move {
logging!(info, Type::Setup, true, "开始检查单例实例...");
match timeout(Duration::from_secs(3), server::check_singleton()).await {
Ok(result) => {
if result.is_err() {
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
if let Some(app_handle) = AppHandleManager::global().get() {
app_handle.exit(0);
} else {
std::process::exit(0);
}
} else {
logging!(info, Type::Setup, true, "未检测到其他应用实例");
}
}
Err(_) => {
logging!(
warn,
Type::Setup,
true,
"单例检查超时,假定没有其他实例运行"
);
}
// 单例检测
let app_exists: bool = tauri::async_runtime::block_on(async move {
if server::check_singleton().await.is_err() {
println!("app exists");
true
} else {
false
}
});
if app_exists {
return;
}
#[cfg(target_os = "linux")]
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
@@ -125,97 +99,40 @@ pub fn run() {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
None,
))
.plugin(tauri_plugin_window_state::Builder::new().build())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.setup(|app| {
logging!(info, Type::Setup, true, "开始应用初始化...");
let mut auto_start_plugin_builder = tauri_plugin_autostart::Builder::new();
#[cfg(target_os = "macos")]
{
auto_start_plugin_builder = auto_start_plugin_builder
.macos_launcher(MacosLauncher::LaunchAgent)
.app_name(app.config().identifier.clone());
}
let _ = app.handle().plugin(auto_start_plugin_builder.build());
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
{
use tauri_plugin_deep_link::DeepLinkExt;
logging!(info, Type::Setup, true, "注册深层链接...");
logging_error!(Type::System, true, app.deep_link().register_all());
log_err!(app.deep_link().register_all());
}
app.deep_link().on_open_url(|event| {
AsyncHandler::spawn(move || {
let url = event.urls().first().map(|u| u.to_string());
async move {
if let Some(url) = url {
logging_error!(Type::Setup, true, resolve_scheme(url).await);
}
tauri::async_runtime::spawn(async move {
if let Some(url) = event.urls().first() {
log_err!(resolve_scheme(url.to_string()).await);
}
});
});
// 窗口管理
logging!(info, Type::Setup, true, "初始化窗口状态管理...");
let window_state_plugin = tauri_plugin_window_state::Builder::new()
.with_filename("window_state.json")
.with_state_flags(tauri_plugin_window_state::StateFlags::default())
.build();
let _ = app.handle().plugin(window_state_plugin);
// 异步处理
let app_handle = app.handle().clone();
AsyncHandler::spawn(move || async move {
logging!(info, Type::Setup, true, "异步执行应用设置...");
match timeout(
Duration::from_secs(30),
resolve::resolve_setup_async(&app_handle),
)
.await
{
Ok(_) => {
logging!(info, Type::Setup, true, "应用设置成功完成");
}
Err(_) => {
logging!(
error,
Type::Setup,
true,
"应用设置超时(30秒),继续执行后续流程"
);
}
}
tauri::async_runtime::block_on(async move {
resolve::resolve_setup(app).await;
});
logging!(info, Type::Setup, true, "执行主要设置操作...");
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
AppHandleManager::global().init(app.handle().clone());
logging!(info, Type::Setup, true, "初始化核心句柄...");
core::handle::Handle::global().init(app.handle());
logging!(info, Type::Setup, true, "初始化配置...");
if let Err(e) = utils::init::init_config() {
logging!(error, Type::Setup, true, "初始化配置失败: {}", e);
}
logging!(info, Type::Setup, true, "初始化资源...");
if let Err(e) = utils::init::init_resources() {
logging!(error, Type::Setup, true, "初始化资源失败: {}", e);
}
app.manage(Mutex::new(state::proxy::CmdProxyState::default()));
app.manage(Mutex::new(state::lightweight::LightWeightState::default()));
logging!(info, Type::Setup, true, "初始化完成,继续执行");
Ok(())
})
.invoke_handler(tauri::generate_handler![
@@ -228,29 +145,11 @@ pub fn run() {
cmd::open_core_dir,
cmd::get_portable_flag,
cmd::get_network_interfaces,
cmd::get_system_hostname,
cmd::restart_app,
// 内核管理
cmd::start_core,
cmd::stop_core,
cmd::restart_core,
// 启动命令
cmd::notify_ui_ready,
cmd::update_ui_stage,
cmd::reset_ui_ready_state,
cmd::restart_app,
// 添加新的命令
cmd::get_running_mode,
cmd::get_app_uptime,
cmd::get_auto_launch_status,
cmd::is_admin,
// 添加轻量模式相关命令
cmd::entry_lightweight_mode,
cmd::exit_lightweight_mode,
// service 管理
cmd::install_service,
cmd::uninstall_service,
cmd::reinstall_service,
cmd::repair_service,
cmd::is_service_available,
// clash
cmd::get_clash_info,
cmd::patch_clash_config,
@@ -263,13 +162,11 @@ pub fn run() {
cmd::invoke_uwp_tool,
cmd::copy_clash_env,
cmd::get_proxies,
cmd::force_refresh_proxies,
cmd::get_providers_proxies,
cmd::save_dns_config,
cmd::apply_dns_config,
cmd::check_dns_config_exists,
cmd::get_dns_config_content,
cmd::validate_dns_config,
// verge
cmd::get_verge_config,
cmd::patch_verge_config,
@@ -293,7 +190,6 @@ pub fn run() {
cmd::delete_profile,
cmd::read_profile_file,
cmd::save_profile_file,
cmd::get_next_update_time,
// script validation
cmd::script_validate_notice,
cmd::validate_script_file,
@@ -307,13 +203,6 @@ pub fn run() {
cmd::restore_webdav_backup,
// export diagnostic info for issue reporting
cmd::export_diagnostic_info,
// get system info for display
cmd::get_system_info,
// media unlock checker
cmd::get_unlock_items,
cmd::check_media_unlock,
// light-weight model
cmd::entry_lightweight_mode,
]);
#[cfg(debug_assertions)]
@@ -321,36 +210,21 @@ pub fn run() {
builder = builder.plugin(devtools);
}
// Macos Application Menu
#[cfg(target_os = "macos")]
{
// Temporary Achived due to cannot CMD+C/V/A
}
let app = builder
.build(tauri::generate_context!())
.expect("error while running tauri application");
app.run(|app_handle, e| match e {
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
logging!(info, Type::System, true, "应用就绪或恢复");
AppHandleManager::global().init(app_handle.clone());
#[cfg(target_os = "macos")]
{
if let Some(window) = AppHandleManager::global()
.get_handle()
.get_webview_window("main")
{
logging!(info, Type::Window, true, "设置macOS窗口标题");
let _ = window.set_title("Clash Verge");
}
let main_window = AppHandleManager::global().get_handle().get_webview_window("main").unwrap();
let _ = main_window.set_title("Clash Verge");
}
}
#[cfg(target_os = "macos")]
tauri::RunEvent::Reopen {
has_visible_windows,
..
} => {
tauri::RunEvent::Reopen { has_visible_windows, .. } => {
if !has_visible_windows {
AppHandleManager::global().set_activation_policy_regular();
}
@@ -361,13 +235,6 @@ pub fn run() {
api.prevent_exit();
}
}
tauri::RunEvent::Exit => {
// avoid duplicate cleanup
if core::handle::Handle::global().is_exiting() {
return;
}
feat::clean();
}
tauri::RunEvent::WindowEvent { label, event, .. } => {
if label == "main" {
match event {
@@ -377,76 +244,54 @@ pub fn run() {
if core::handle::Handle::global().is_exiting() {
return;
}
log::info!(target: "app", "closing window...");
println!("closing window...");
api.prevent_close();
if let Some(window) = core::handle::Handle::global().get_window() {
let _ = window.hide();
} else {
logging!(warn, Type::Window, true, "尝试隐藏窗口但窗口不存在");
}
let window = core::handle::Handle::global().get_window().unwrap();
let _ = window.hide();
}
tauri::WindowEvent::Focused(true) => {
#[cfg(target_os = "macos")]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+Q", "quit")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+W", "hide")
);
log_err!(hotkey::Hotkey::global().register("CMD+Q", "quit"));
}
#[cfg(not(target_os = "macos"))]
{
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
};
{
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
if !is_enable_global_hotkey {
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().init())
log_err!(hotkey::Hotkey::global().init())
}
}
}
tauri::WindowEvent::Focused(false) => {
#[cfg(target_os = "macos")]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
}
#[cfg(not(target_os = "macos"))]
{
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
};
{
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
if !is_enable_global_hotkey {
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().reset())
log_err!(hotkey::Hotkey::global().reset())
}
}
}
tauri::WindowEvent::Destroyed => {
#[cfg(target_os = "macos")]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
};
}
_ => {}
}

View File

@@ -1,265 +0,0 @@
use crate::{
config::Config,
core::{handle, timer::Timer, tray::Tray},
log_err, logging,
state::lightweight::LightWeightState,
utils::logging::Type,
};
#[cfg(target_os = "macos")]
use crate::logging_error;
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use anyhow::{Context, Result};
use delay_timer::prelude::TaskBuilder;
use std::sync::Mutex;
use tauri::{Listener, Manager};
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
fn with_lightweight_status<F, R>(f: F) -> R
where
F: FnOnce(&mut LightWeightState) -> R,
{
let app_handle = handle::Handle::global().app_handle().unwrap();
let state = app_handle.state::<Mutex<LightWeightState>>();
let mut guard = state.lock().unwrap();
f(&mut guard)
}
pub fn run_once_auto_lightweight() {
LightWeightState::default().run_once_time(|| {
let is_silent_start = Config::verge().data().enable_silent_start.unwrap_or(true);
let enable_auto = Config::verge()
.data()
.enable_auto_light_weight_mode
.unwrap_or(true);
if enable_auto && is_silent_start {
logging!(
info,
Type::Lightweight,
true,
"正常创建窗口和添加定时器监听器"
);
set_lightweight_mode(false);
disable_auto_light_weight_mode();
// 触发托盘更新
if let Err(e) = Tray::global().update_part() {
log::warn!("Failed to update tray: {}", e);
}
}
});
}
pub fn auto_lightweight_mode_init() {
if let Some(app_handle) = handle::Handle::global().app_handle() {
let _ = app_handle.state::<Mutex<LightWeightState>>();
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
let enable_auto = { Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
if enable_auto && is_silent_start {
logging!(info, Type::Lightweight, true, "自动轻量模式静默启动");
set_lightweight_mode(true);
enable_auto_light_weight_mode();
// 确保托盘状态更新
if let Err(e) = Tray::global().update_part() {
log::warn!("Failed to update tray: {}", e);
}
}
}
}
// 检查是否处于轻量模式
pub fn is_in_lightweight_mode() -> bool {
with_lightweight_status(|state| state.is_lightweight)
}
// 设置轻量模式状态
fn set_lightweight_mode(value: bool) {
with_lightweight_status(|state| {
state.set_lightweight_mode(value);
});
// 触发托盘更新
if let Err(e) = Tray::global().update_part() {
log::warn!("Failed to update tray: {}", e);
}
}
pub fn enable_auto_light_weight_mode() {
Timer::global().init().unwrap();
logging!(info, Type::Lightweight, true, "开启自动轻量模式");
setup_window_close_listener();
setup_webview_focus_listener();
}
pub fn disable_auto_light_weight_mode() {
logging!(info, Type::Lightweight, true, "关闭自动轻量模式");
let _ = cancel_light_weight_timer();
cancel_window_close_listener();
}
pub fn entry_lightweight_mode() {
use crate::utils::window_manager::WindowManager;
let result = WindowManager::hide_main_window();
logging!(
info,
Type::Lightweight,
true,
"轻量模式隐藏窗口结果: {:?}",
result
);
if let Some(window) = handle::Handle::global().get_window() {
if let Some(webview) = window.get_webview_window("main") {
let _ = webview.destroy();
}
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_accessory();
logging!(info, Type::Lightweight, true, "轻量模式已开启");
}
set_lightweight_mode(true);
let _ = cancel_light_weight_timer();
// 更新托盘显示
let _tray = crate::core::tray::Tray::global();
}
// 添加从轻量模式恢复的函数
pub fn exit_lightweight_mode() {
// 确保当前确实处于轻量模式才执行退出操作
if !is_in_lightweight_mode() {
logging!(info, Type::Lightweight, true, "当前不在轻量模式,无需退出");
return;
}
set_lightweight_mode(false);
logging!(info, Type::Lightweight, true, "正在退出轻量模式");
// macOS激活策略
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_regular();
// 重置UI就绪状态
crate::utils::resolve::reset_ui_ready();
// 更新托盘显示
let _tray = crate::core::tray::Tray::global();
}
#[cfg(target_os = "macos")]
pub fn add_light_weight_timer() {
logging_error!(Type::Lightweight, setup_light_weight_timer());
}
fn setup_window_close_listener() -> u32 {
if let Some(window) = handle::Handle::global().get_window() {
let handler = window.listen("tauri://close-requested", move |_event| {
let _ = setup_light_weight_timer();
logging!(
info,
Type::Lightweight,
true,
"监听到关闭请求,开始轻量模式计时"
);
});
return handler;
}
0
}
fn setup_webview_focus_listener() -> u32 {
if let Some(window) = handle::Handle::global().get_window() {
let handler = window.listen("tauri://focus", move |_event| {
log_err!(cancel_light_weight_timer());
logging!(
info,
Type::Lightweight,
"监听到窗口获得焦点,取消轻量模式计时"
);
});
return handler;
}
0
}
fn cancel_window_close_listener() {
if let Some(window) = handle::Handle::global().get_window() {
window.unlisten(setup_window_close_listener());
logging!(info, Type::Lightweight, true, "取消了窗口关闭监听");
}
}
fn setup_light_weight_timer() -> Result<()> {
Timer::global().init()?;
let once_by_minutes = Config::verge()
.latest()
.auto_light_weight_minutes
.unwrap_or(10);
// 获取task_id
let task_id = {
let mut timer_count = Timer::global().timer_count.lock();
let id = *timer_count;
*timer_count += 1;
id
};
// 创建任务
let task = TaskBuilder::default()
.set_task_id(task_id)
.set_maximum_parallel_runnable_num(1)
.set_frequency_once_by_minutes(once_by_minutes)
.spawn_async_routine(move || async move {
logging!(info, Type::Timer, true, "计时器到期,开始进入轻量模式");
entry_lightweight_mode();
})
.context("failed to create timer task")?;
// 添加任务到定时器
{
let delay_timer = Timer::global().delay_timer.write();
delay_timer
.add_task(task)
.context("failed to add timer task")?;
}
// 更新任务映射
{
let mut timer_map = Timer::global().timer_map.write();
let timer_task = crate::core::timer::TimerTask {
task_id,
interval_minutes: once_by_minutes,
last_run: chrono::Local::now().timestamp(),
};
timer_map.insert(LIGHT_WEIGHT_TASK_UID.to_string(), timer_task);
}
logging!(
info,
Type::Timer,
true,
"计时器已设置,{} 分钟后将自动进入轻量模式",
once_by_minutes
);
Ok(())
}
fn cancel_light_weight_timer() -> Result<()> {
let mut timer_map = Timer::global().timer_map.write();
let delay_timer = Timer::global().delay_timer.write();
if let Some(task) = timer_map.remove(LIGHT_WEIGHT_TASK_UID) {
delay_timer
.remove_task(task.task_id)
.context("failed to remove timer task")?;
logging!(info, Type::Timer, true, "计时器已取消");
}
Ok(())
}

View File

@@ -1,94 +1,43 @@
use crate::config::Config;
use mihomo_api;
use once_cell::sync::Lazy;
use parking_lot::{Mutex, RwLock};
use std::time::{Duration, Instant};
use tauri::http::HeaderMap;
use once_cell::sync::{Lazy, OnceCell};
use std::sync::Mutex;
use tauri::http::{HeaderMap, HeaderValue};
#[cfg(target_os = "macos")]
use tauri::http::HeaderValue;
// 缓存的最大有效期5秒
const CACHE_TTL: Duration = Duration::from_secs(5);
use tokio_tungstenite::tungstenite::http;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Rate {
pub up: u64,
pub down: u64,
}
// 缓存MihomoManager实例
struct MihomoCache {
manager: mihomo_api::MihomoManager,
created_at: Instant,
server: String,
}
// 使用RwLock替代Mutex允许多个读取操作并发进行
pub struct MihomoManager {
mihomo_cache: RwLock<Option<MihomoCache>>,
create_lock: Mutex<()>,
mihomo: Mutex<OnceCell<mihomo_api::MihomoManager>>,
}
impl MihomoManager {
fn __global() -> &'static MihomoManager {
static INSTANCE: Lazy<MihomoManager> = Lazy::new(|| MihomoManager {
mihomo_cache: RwLock::new(None),
create_lock: Mutex::new(()),
mihomo: Mutex::new(OnceCell::new()),
});
&INSTANCE
}
pub fn global() -> mihomo_api::MihomoManager {
let instance = MihomoManager::__global();
let (current_server, headers) = MihomoManager::get_clash_client_info().unwrap();
// 尝试从缓存读取(只需读锁)
{
let cache = instance.mihomo_cache.read();
if let Some(cache_entry) = &*cache {
let (current_server, _) = MihomoManager::get_clash_client_info()
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
// 检查缓存是否有效
if cache_entry.server == current_server
&& cache_entry.created_at.elapsed() < CACHE_TTL
{
return cache_entry.manager.clone();
}
let lock = instance.mihomo.lock().unwrap();
if let Some(mihomo) = lock.get() {
if mihomo.get_mihomo_server() == current_server {
return mihomo.clone();
}
}
// 缓存无效,获取创建锁
let _create_guard = instance.create_lock.lock();
// 再次检查缓存(双重检查锁定模式)
{
let cache = instance.mihomo_cache.read();
if let Some(cache_entry) = &*cache {
let (current_server, _) = MihomoManager::get_clash_client_info()
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
if cache_entry.server == current_server
&& cache_entry.created_at.elapsed() < CACHE_TTL
{
return cache_entry.manager.clone();
}
}
}
// 创建新实例
let (current_server, headers) = MihomoManager::get_clash_client_info()
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
let manager = mihomo_api::MihomoManager::new(current_server.clone(), headers);
// 更新缓存
{
let mut cache = instance.mihomo_cache.write();
*cache = Some(MihomoCache {
manager: manager.clone(),
created_at: Instant::now(),
server: current_server,
});
}
manager
lock.set(mihomo_api::MihomoManager::new(current_server, headers))
.ok();
lock.get().unwrap().clone()
}
}
@@ -105,32 +54,17 @@ impl MihomoManager {
Some((server, headers))
}
// 提供默认值的版本避免在connection_info为None时panic
#[cfg(target_os = "macos")]
fn get_clash_client_info_or_default() -> (String, HeaderMap) {
Self::get_clash_client_info().unwrap_or_else(|| {
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
("http://127.0.0.1:9090".to_string(), headers)
})
}
#[cfg(target_os = "macos")]
pub fn get_traffic_ws_url() -> (String, HeaderValue) {
let (url, headers) = MihomoManager::get_clash_client_info_or_default();
let (url, headers) = MihomoManager::get_clash_client_info().unwrap();
let ws_url = url.replace("http://", "ws://") + "/traffic";
let auth = headers
.get("Authorization")
.map(|val| val.to_str().unwrap_or("").to_string())
.unwrap_or_default();
// 创建默认的空HeaderValue而不是使用unwrap_or_default
let token = match HeaderValue::from_str(&auth) {
Ok(v) => v,
Err(_) => HeaderValue::from_static(""),
};
(ws_url, token)
.unwrap()
.to_str()
.unwrap()
.to_string();
let token = http::header::HeaderValue::from_str(&auth).unwrap();
return (ws_url, token);
}
}

View File

@@ -1,3 +1,2 @@
pub mod lightweight;
pub mod mihomo;
pub mod sysinfo;
pub mod mihomo;

View File

@@ -1,7 +1,4 @@
use crate::{
cmd::system,
core::{handle, CoreManager},
};
use crate::core::{handle, CoreManager};
use std::fmt::{self, Debug, Formatter};
use sysinfo::System;
@@ -12,15 +9,14 @@ pub struct PlatformSpecification {
system_arch: String,
verge_version: String,
running_mode: String,
is_admin: bool,
}
impl Debug for PlatformSpecification {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}\nIs Admin: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch, self.verge_version, self.running_mode, self.is_admin
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch, self.verge_version, self.running_mode
)
}
}
@@ -30,15 +26,23 @@ impl PlatformSpecification {
let system_name = System::name().unwrap_or("Null".into());
let system_version = System::long_os_version().unwrap_or("Null".into());
let system_kernel_version = System::kernel_version().unwrap_or("Null".into());
let system_arch = System::cpu_arch();
let system_arch = std::env::consts::ARCH.to_string();
let handler = handle::Handle::global().app_handle().unwrap();
let verge_version = handler.package_info().version.to_string();
let config = handler.config();
let verge_version = config.version.clone().unwrap_or("Null".into());
// 使用默认值避免在同步上下文中执行异步操作
let running_mode = "NotRunning".to_string();
// Get running mode asynchronously
let running_mode = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
match CoreManager::global().get_running_mode().await {
crate::core::RunningMode::Service => "Service".to_string(),
crate::core::RunningMode::Sidecar => "Sidecar".to_string(),
crate::core::RunningMode::NotRunning => "Not Running".to_string(),
}
})
});
let is_admin = system::is_admin().unwrap_or_default();
Self {
system_name,
@@ -47,17 +51,6 @@ impl PlatformSpecification {
system_arch,
verge_version,
running_mode,
is_admin,
}
}
// 异步方法来获取完整的系统信息
pub async fn new_async() -> Self {
let mut info = Self::new();
let running_mode = CoreManager::global().get_running_mode().await;
info.running_mode = running_mode.to_string();
info
}
}

View File

@@ -1,14 +0,0 @@
use std::future::Future;
use tauri::{async_runtime, async_runtime::JoinHandle};
pub struct AsyncHandler;
impl AsyncHandler {
pub fn spawn<F, Fut>(f: F) -> JoinHandle<()>
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
async_runtime::spawn(f())
}
}

View File

@@ -1,2 +0,0 @@
mod async_handler;
pub use async_handler::AsyncHandler;

View File

@@ -1,44 +0,0 @@
use std::sync::{Arc, Once, OnceLock};
use crate::{logging, utils::logging::Type};
#[derive(Clone)]
pub struct LightWeightState {
#[allow(unused)]
once: Arc<Once>,
pub is_lightweight: bool,
}
impl LightWeightState {
pub fn new() -> Self {
Self {
once: Arc::new(Once::new()),
is_lightweight: false,
}
}
#[allow(unused)]
pub fn run_once_time<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
self.once.call_once(f);
}
pub fn set_lightweight_mode(&mut self, value: bool) -> &Self {
self.is_lightweight = value;
if value {
logging!(info, Type::Lightweight, true, "轻量模式已开启");
} else {
logging!(info, Type::Lightweight, true, "轻量模式已关闭");
}
self
}
}
impl Default for LightWeightState {
fn default() -> Self {
static INSTANCE: OnceLock<LightWeightState> = OnceLock::new();
INSTANCE.get_or_init(LightWeightState::new).clone()
}
}

View File

@@ -1,5 +0,0 @@
// Tauri Manager 会进行 Arc 管理,无需额外 Arc
// https://tauri.app/develop/state-management/#do-you-need-arc
pub mod lightweight;
pub mod proxy;

View File

@@ -1,19 +0,0 @@
use serde_json::Value;
pub struct CmdProxyState {
pub last_refresh_time: std::time::Instant,
pub need_refresh: bool,
pub proxies: Box<Value>,
pub providers_proxies: Box<Value>,
}
impl Default for CmdProxyState {
fn default() -> Self {
Self {
last_refresh_time: std::time::Instant::now(),
need_refresh: true,
proxies: Box::new(Value::Null),
providers_proxies: Box::new(Value::Null),
}
}
}

View File

@@ -1,116 +0,0 @@
#[cfg(target_os = "windows")]
use anyhow::{anyhow, Result};
#[cfg(target_os = "windows")]
use log::info;
#[cfg(target_os = "windows")]
use std::{fs, path::Path, path::PathBuf};
/// Windows 下的开机启动文件夹路径
#[cfg(target_os = "windows")]
pub fn get_startup_dir() -> Result<PathBuf> {
let appdata = std::env::var("APPDATA").map_err(|_| anyhow!("无法获取 APPDATA 环境变量"))?;
let startup_dir = Path::new(&appdata)
.join("Microsoft")
.join("Windows")
.join("Start Menu")
.join("Programs")
.join("Startup");
if !startup_dir.exists() {
return Err(anyhow!("Startup 目录不存在: {:?}", startup_dir));
}
Ok(startup_dir)
}
/// 获取当前可执行文件路径
#[cfg(target_os = "windows")]
pub fn get_exe_path() -> Result<PathBuf> {
let exe_path =
std::env::current_exe().map_err(|e| anyhow!("无法获取当前可执行文件路径: {}", e))?;
Ok(exe_path)
}
/// 创建快捷方式
#[cfg(target_os = "windows")]
pub fn create_shortcut() -> Result<()> {
let exe_path = get_exe_path()?;
let startup_dir = get_startup_dir()?;
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
// 如果快捷方式已存在,直接返回成功
if shortcut_path.exists() {
info!(target: "app", "启动快捷方式已存在");
return Ok(());
}
// 使用 PowerShell 创建快捷方式
let powershell_command = format!(
"$WshShell = New-Object -ComObject WScript.Shell; \
$Shortcut = $WshShell.CreateShortcut('{}'); \
$Shortcut.TargetPath = '{}'; \
$Shortcut.Save()",
shortcut_path.to_string_lossy().replace("\\", "\\\\"),
exe_path.to_string_lossy().replace("\\", "\\\\")
);
let output = std::process::Command::new("powershell")
.args(["-Command", &powershell_command])
.output()
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;
if !output.status.success() {
let error_msg = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("创建快捷方式失败: {}", error_msg));
}
info!(target: "app", "成功创建启动快捷方式");
Ok(())
}
/// 删除快捷方式
#[cfg(target_os = "windows")]
pub fn remove_shortcut() -> Result<()> {
let startup_dir = get_startup_dir()?;
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
// 如果快捷方式不存在,直接返回成功
if !shortcut_path.exists() {
info!(target: "app", "启动快捷方式不存在,无需删除");
return Ok(());
}
// 删除快捷方式
fs::remove_file(&shortcut_path).map_err(|e| anyhow!("删除快捷方式失败: {}", e))?;
info!(target: "app", "成功删除启动快捷方式");
Ok(())
}
/// 检查快捷方式是否存在
#[cfg(target_os = "windows")]
pub fn is_shortcut_enabled() -> Result<bool> {
let startup_dir = get_startup_dir()?;
let shortcut_path = startup_dir.join("Clash-Verge.lnk");
Ok(shortcut_path.exists())
}
// 非 Windows 平台使用的空方法
// #[cfg(not(target_os = "windows"))]
// pub fn create_shortcut() -> Result<()> {
// Ok(())
// }
// #[cfg(not(target_os = "windows"))]
// pub fn remove_shortcut() -> Result<()> {
// Ok(())
// }
// #[cfg(not(target_os = "windows"))]
// pub fn is_shortcut_enabled() -> Result<bool> {
// Ok(false)
// }

View File

@@ -1,7 +1,8 @@
use crate::core::handle;
use anyhow::Result;
use once_cell::sync::OnceCell;
use std::{fs, path::PathBuf};
use std::fs;
use std::path::PathBuf;
use tauri::Manager;
#[cfg(not(feature = "verge-dev"))]
@@ -49,60 +50,12 @@ pub fn app_home_dir() -> Result<PathBuf> {
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
}
// 避免在Handle未初始化时崩溃
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "app_handle not initialized, using default path");
// 使用可执行文件目录作为备用
let exe_path = tauri::utils::platform::current_exe()?;
let exe_dir = exe_path
.parent()
.ok_or(anyhow::anyhow!("failed to get executable directory"))?;
// 使用系统临时目录 + 应用ID
#[cfg(target_os = "windows")]
{
if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
let path = PathBuf::from(local_app_data).join(APP_ID);
return Ok(path);
}
}
#[cfg(target_os = "macos")]
{
if let Some(home) = std::env::var_os("HOME") {
let path = PathBuf::from(home)
.join("Library")
.join("Application Support")
.join(APP_ID);
return Ok(path);
}
}
#[cfg(target_os = "linux")]
{
if let Some(home) = std::env::var_os("HOME") {
let path = PathBuf::from(home)
.join(".local")
.join("share")
.join(APP_ID);
return Ok(path);
}
}
// 如果无法获取系统目录,则回退到可执行文件目录
let fallback_dir = PathBuf::from(exe_dir).join(".config").join(APP_ID);
log::warn!(target: "app", "Using fallback data directory: {:?}", fallback_dir);
return Ok(fallback_dir);
}
};
let app_handle = handle::Handle::global().app_handle().unwrap();
match app_handle.path().data_dir() {
Ok(dir) => Ok(dir.join(APP_ID)),
Err(e) => {
log::error!(target: "app", "Failed to get the app home directory: {}", e);
log::error!(target:"app", "Failed to get the app home directory: {}", e);
Err(anyhow::anyhow!("Failed to get the app homedirectory"))
}
}
@@ -110,24 +63,11 @@ pub fn app_home_dir() -> Result<PathBuf> {
/// get the resources dir
pub fn app_resources_dir() -> Result<PathBuf> {
// 避免在Handle未初始化时崩溃
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::warn!(target: "app", "app_handle not initialized in app_resources_dir, using fallback");
// 使用可执行文件目录作为备用
let exe_dir = tauri::utils::platform::current_exe()?
.parent()
.ok_or(anyhow::anyhow!("failed to get executable directory"))?
.to_path_buf();
return Ok(exe_dir.join("resources"));
}
};
let app_handle = handle::Handle::global().app_handle().unwrap();
match app_handle.path().resource_dir() {
Ok(dir) => Ok(dir.join("resources")),
Err(e) => {
log::error!(target: "app", "Failed to get the resource directory: {}", e);
log::error!(target:"app", "Failed to get the resource directory: {}", e);
Err(anyhow::anyhow!("Failed to get the resource directory"))
}
}
@@ -138,36 +78,6 @@ pub fn app_profiles_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("profiles"))
}
/// icons dir
pub fn app_icons_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("icons"))
}
pub fn find_target_icons(target: &str) -> Result<Option<String>> {
let icons_dir = app_icons_dir()?;
let mut matching_files = Vec::new();
for entry in fs::read_dir(icons_dir)? {
let entry = entry?;
let path = entry.path();
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
if file_name.starts_with(target)
&& (file_name.ends_with(".ico") || file_name.ends_with(".png"))
{
matching_files.push(path);
}
}
}
if matching_files.is_empty() {
Ok(None)
} else {
let first = path_to_str(matching_files.first().unwrap())?;
Ok(Some(first.to_string()))
}
}
/// logs dir
pub fn app_logs_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("logs"))
@@ -187,14 +97,12 @@ pub fn profiles_path() -> Result<PathBuf> {
#[cfg(target_os = "macos")]
pub fn service_path() -> Result<PathBuf> {
let res_dir = app_resources_dir()?;
Ok(res_dir.join("clash-verge-service"))
Ok(app_resources_dir()?.join("clash-verge-service"))
}
#[cfg(windows)]
pub fn service_path() -> Result<PathBuf> {
let res_dir = app_resources_dir()?;
Ok(res_dir.join("clash-verge-service.exe"))
Ok(app_resources_dir()?.join("clash-verge-service.exe"))
}
pub fn service_log_file() -> Result<PathBuf> {

View File

@@ -0,0 +1,40 @@
use crate::log_err;
use anyhow;
use std::{
backtrace::{Backtrace, BacktraceStatus},
thread,
};
pub fn redirect_panic_to_log() {
std::panic::set_hook(Box::new(move |panic_info| {
let thread = thread::current();
let thread_name = thread.name().unwrap_or("<unnamed>");
let payload = panic_info.payload();
let payload = if let Some(s) = payload.downcast_ref::<&str>() {
&**s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
&format!("{:?}", payload)
};
let location = panic_info
.location()
.map(|l| l.to_string())
.unwrap_or("unknown location".to_string());
let backtrace = Backtrace::capture();
let backtrace = if backtrace.status() == BacktraceStatus::Captured {
&format!("stack backtrace:\n{}", backtrace)
} else {
"note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace"
};
let err: Result<(), anyhow::Error> = Err(anyhow::anyhow!(format!(
"thread '{}' panicked at {}:\n{}\n{}",
thread_name, location, payload, backtrace
)));
log_err!(err);
}));
}

View File

@@ -1,8 +1,8 @@
use crate::{enhance::seq::SeqMap, logging, utils::logging::Type};
use crate::enhance::seq::SeqMap;
use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::Mapping;
use serde_yaml::{Mapping, Value};
use std::{fs, path::PathBuf, str::FromStr};
/// read data from yaml as struct T
@@ -22,41 +22,19 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
})
}
/// read mapping from yaml
/// read mapping from yaml fix #165
pub fn read_mapping(path: &PathBuf) -> Result<Mapping> {
if !path.exists() {
bail!("file not found \"{}\"", path.display());
}
let mut val: Value = read_yaml(path)?;
val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
let yaml_str = fs::read_to_string(path)
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
// YAML语法检查
match serde_yaml::from_str::<serde_yaml::Value>(&yaml_str) {
Ok(mut val) => {
val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
Ok(val
.as_mapping()
.ok_or(anyhow!(
"failed to transform to yaml mapping \"{}\"",
path.display()
))?
.to_owned())
}
Err(err) => {
let error_msg = format!("YAML syntax error in {}: {}", path.display(), err);
logging!(error, Type::Config, true, "{}", error_msg);
crate::core::handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
&error_msg,
);
bail!("YAML syntax error: {}", err)
}
}
Ok(val
.as_mapping()
.ok_or(anyhow!(
"failed to transform to yaml mapping \"{}\"",
path.display()
))?
.to_owned())
}
/// read mapping from yaml fix #165
@@ -158,6 +136,52 @@ pub fn linux_elevator() -> String {
}
}
#[macro_export]
macro_rules! error {
($result: expr) => {
log::error!(target: "app", "{}", $result);
};
}
#[macro_export]
macro_rules! log_err {
($result: expr) => {
if let Err(err) = $result {
log::error!(target: "app", "{err}");
}
};
($result: expr, $err_str: expr) => {
if let Err(_) = $result {
log::error!(target: "app", "{}", $err_str);
}
};
}
#[macro_export]
macro_rules! trace_err {
($result: expr, $err_str: expr) => {
if let Err(err) = $result {
log::trace!(target: "app", "{}, err {}", $err_str, err);
}
}
}
/// wrap the anyhow error
/// transform the error to String
#[macro_export]
macro_rules! wrap_err {
($stat: expr) => {
match $stat {
Ok(a) => Ok(a),
Err(err) => {
log::error!(target: "app", "{}", err.to_string());
Err(format!("{}", err.to_string()))
}
}
};
}
/// return the string literal error
#[macro_export]
macro_rules! ret_err {
@@ -183,29 +207,28 @@ macro_rules! t {
/// # Examples
/// ```not_run
/// format_bytes_speed(1000) // returns "1000B/s"
/// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s"
/// ```
/// ```
#[cfg(target_os = "macos")]
pub fn format_bytes_speed(speed: u64) -> String {
const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
let mut size = speed as f64;
let mut unit_index = 0;
while size >= 1000.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
if speed < 1024 {
format!("{}B/s", speed)
} else if speed < 1024 * 1024 {
format!("{:.1}KB/s", speed as f64 / 1024.0)
} else if speed < 1024 * 1024 * 1024 {
format!("{:.1}MB/s", speed as f64 / 1024.0 / 1024.0)
} else {
format!("{:.1}GB/s", speed as f64 / 1024.0 / 1024.0 / 1024.0)
}
format!("{:.1}{}/s", size, UNITS[unit_index])
}
#[cfg(target_os = "macos")]
#[test]
fn test_format_bytes_speed() {
assert_eq!(format_bytes_speed(0), "0.0B/s");
assert_eq!(format_bytes_speed(1023), "1.0KB/s");
assert_eq!(format_bytes_speed(0), "0B/s");
assert_eq!(format_bytes_speed(1023), "1023B/s");
assert_eq!(format_bytes_speed(1024), "1.0KB/s");
assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s");
assert_eq!(format_bytes_speed(1024 * 1024 * 1024), "1.0GB/s");

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