Compare commits

..

2447 Commits
v2.1.2 ... main

Author SHA1 Message Date
wonfen
84fbccbfd9 release v2.2.3 2025-04-09 13:37:17 +08:00
TianHua Liu
49c81f6201
fix: the first function call creates multiple axios instances (#3273) 2025-04-07 16:50:53 +08:00
Tunglies
c894a15d13 chore: update UPDATELOG 2025-04-06 02:03:12 +08:00
Tunglies
196b887381 Adjust text and shadow colors in tray speed rate display with colorful
icon
2025-04-06 02:00:40 +08:00
Tunglies
2ad20ed239 Merge branch 'tunglies' into dev 2025-04-05 20:00:55 +08:00
Tunglies
98de91771e Simplify tray icon template logic and adjust text colors 2025-04-05 19:54:32 +08:00
Tunglies
9dfd9bad20 Remove color detection for tray icons
Always use white text with black shadow regardless of icon type
2025-04-05 16:07:16 +08:00
Tunglies
0b8d08d13b Update logging macros usage in timer module
Replace log macros with custom logging macros in timer module
2025-04-05 11:36:38 +08:00
Tunglies
0de304d4e3 Fix and Optimize
fix: #3187 and #3096, silent startup can not trigger lightweight mode
fix: optimize startup initializing processing
2025-04-05 10:28:27 +08:00
Tunglies
55f1766ebc Change logging level from info to debug for hotkeys 2025-04-05 09:19:44 +08:00
Langning Chen
6d1a8fb264
Fix traffic graph time mismatch (#3201) 2025-04-04 17:52:18 +08:00
wonfen
d3958594d9 fix: #2609, avoid URL encoding issues by directly parsing query parameters 2025-04-04 13:50:21 +08:00
wonfen
b5952f320b fix: #3244, remove test directory, simplify resource initialization 2025-04-04 13:18:17 +08:00
wonfen
98be9621a6 feat: retry subscription fetch using Clash proxy on failure 2025-04-03 14:55:28 +08:00
wonfen
e4eb13ce22 chore: update default DNS override configuration 2025-04-03 14:55:28 +08:00
Tunglies
ecf2da7c0a chore: update UPDATELOG 2025-04-03 07:35:37 +08:00
wonfen
7d7c8988d7 fix: resolve rendering issue caused by duplicate node names 2025-04-02 12:48:17 +08:00
Tunglies
5e11d36972 Remove real-time window position and size saving 2025-04-01 17:26:27 +08:00
wonfen
3039f39d40 fix: #3227 show traffic chart only when data is available 2025-04-01 13:51:35 +08:00
wonfen
7b5fd104de fix: #3227, use async operations to avoid blocking on 2025-04-01 12:52:59 +08:00
wonfen
30ea408019 feat: enhance system info card with combo mode display 2025-04-01 01:44:59 +08:00
Tunglies
3fa130695c rm: setup_window_state_monitor for temporary due to unkown window control behavior 2025-03-31 21:52:50 +08:00
Tunglies
fece31438a chore: update UPDATELOG 2025-03-31 21:47:06 +08:00
Tunglies
ad1f2bea3b fix: unused Result 2025-03-31 21:44:12 +08:00
Tunglies
cd4bfdd743 feat: change proxy mode to close connections if enable auto close connections 2025-03-31 21:43:29 +08:00
wonfen
cde8c4004f feat: add is_admin field to export_diagnostic_info 2025-03-31 10:34:11 +08:00
wonfen
c53514e060 feat: unify runtime mode detection; support TUN and service installation in admin mode 2025-03-31 08:16:14 +08:00
wonfen
8e99672265 feat: show node count in proxy groups 2025-03-31 04:35:27 +08:00
wonfen
5b9b5cb6a8 fix: add macos missing libc deps 2025-03-31 03:36:58 +08:00
wonfen
62141380d8 chore: update dependencies and bug report template 2025-03-31 03:24:36 +08:00
wonfen
52a15bb281 feat: detect admin mode and warn about auto-start unavailability 2025-03-31 03:22:24 +08:00
Tunglies
b092f74c88 fix(dir): wrong logs dir path 2025-03-30 13:12:42 +08:00
Tunglies
937f43c270 fix: unable to switch subscribtion profile 2025-03-30 12:53:16 +08:00
Tunglies
9b02088918 chore: update UPDATELOG 2025-03-30 12:49:07 +08:00
wonfen
1bd503a654 feat: add error prompt for initial config loading to prevent switching to invalid subscription 2025-03-30 10:56:30 +08:00
Tunglies
c6477dfda4 Update build target from esnext to es2020 2025-03-30 04:19:35 +08:00
Tunglies
4831d88467 rm(lightweight): unused logging 2025-03-29 13:05:58 +08:00
Tunglies
9ebde802d4 Update alpha workflow to trigger on src directory changes 2025-03-29 12:35:49 +08:00
wonfen
a9cccc7b97 feat: add error prompt for loading initial config file 2025-03-29 07:52:46 +08:00
Tunglies
d54a765bd6 chore: update UPDATELOG 2025-03-28 19:41:46 +08:00
Tunglies
5a2751162f fix(linux): can not connect to mihomo core 2025-03-28 19:39:11 +08:00
Tunglies
492a5a6de7 feat(cmd): service return message i18 language support 2025-03-28 11:51:52 +08:00
Tunglies
f6c0f144a6 fix(clippy): clippy warning codes 2025-03-28 11:43:21 +08:00
Tunglies
1c046f3ca3 fix(cmd): service error message return with shorter message 2025-03-28 11:35:50 +08:00
wonfen
4a47c5bb6f fix: unused code on non-MacOS platform 2025-03-28 05:58:02 +08:00
wonfen
b7e01aefb4 perf: optimize homepage traffic chart code, refine UI, and complete i18n 2025-03-28 05:53:18 +08:00
Tunglies
e8e16f7d57 refactor(logging): replace log_err! with structured logging_error! calls
refactor(cm-service): better error handling from Backend to Frontend
2025-03-28 03:39:34 +08:00
wonfen
59caa22431 feat: enhance service startup logic with user preference-based sidecar fallback 2025-03-28 03:20:13 +08:00
Tunglies
e2046f3e48 refact(profile+core): replace println with logging! macros for structured logging 2025-03-28 01:48:55 +08:00
Tunglies
8fdcffc731 refactor(CoreManager) combine duplicated logical 2025-03-28 00:58:34 +08:00
Tunglies
3ec77c6256 chore: update UPDATELOG 2025-03-27 20:52:50 +08:00
Tunglies
b09313756e refactor: optimize total width calculation and format bytes speed function 2025-03-27 20:45:51 +08:00
wonfen
f800e2e3b6 refactor: simplify graph code and adjust text margins 2025-03-27 13:11:39 +08:00
wonfen
daad623855 fix: resolve toggle flicker for "Auto Start" and "DNS Override" 2025-03-27 12:30:15 +08:00
Tunglies
7716e2bc87 rm: unused trait filed 2025-03-27 12:14:27 +08:00
Tunglies
70bd5ec03c refactor: simplify tray icon handling and update return types for icon functions
fix: custom tray icon with speedrate display large gap
2025-03-27 12:04:52 +08:00
Tunglies
ce5c86c3b0 fix: load custom tray icon failed due to #2886 2025-03-27 11:12:08 +08:00
Tunglies
a6a6d9d036 feat: initial and restart core checks service version if available
chore: update UPDATELOG.md
2025-03-27 09:20:15 +08:00
Tunglies
971dd6a2cf fea: optimize vite chunk splitting 2025-03-27 05:09:36 +08:00
Tunglies
42d0ea7e36 chore: remove unused dependencies and notification plugin from the project 2025-03-27 02:59:48 +08:00
Tunglies
006bcffe8c chore: update rust dependence and remove unused dependence, minimized feature requred 2025-03-27 01:04:43 +08:00
Tunglies
ff4101fa47 Revert "feat: front-end use RunningMode enum instead of string literals"
This reverts commit 72806357412d5ac5ce052be573713f26505b4f8f.
2025-03-26 22:17:29 +08:00
Tunglies
7280635741 feat: front-end use RunningMode enum instead of string literals 2025-03-26 22:10:42 +08:00
Tunglies
7ede91599c fix: standardize RunningMode handling between frontend and backend
This commit improves the type consistency between the Rust backend and TypeScript frontend by:

1. Modifying the Rust `get_running_mode()` command to return a String instead of RunningMode enum directly
2. Removing the RunningMode enum and IRunningMode interface from TypeScript types
3. Using string literals for mode comparison in frontend components
4. Standardizing on capitalized mode names (e.g., "Sidecar" instead of "sidecar")

These changes ensure proper serialization/deserialization between backend and frontend,
making the code more maintainable and reducing potential inconsistencies.
2025-03-26 22:04:16 +08:00
Tunglies
6e40dd9862 fix: tray icon and tray rate display expression logic bug 2025-03-26 19:07:09 +08:00
Tunglies
42db9ea0bb chore: enable pre-commit for Rust formatting, pre-push for Rust linter check 2025-03-26 18:59:31 +08:00
Tunglies
ca0cf4552c add: RunningMode Display implementation and TypeScript enum 2025-03-26 17:01:48 +08:00
Tunglies
d91653b218 feat: add config log type and improve window logging 2025-03-26 16:43:26 +08:00
Tunglies
1ace560531 feat: add front-end system service management functions 2025-03-26 16:22:13 +08:00
Tunglies
81968a579d feat: reorganize service commands and implement logging for service management 2025-03-26 15:02:08 +08:00
wonfen
5a0eb56f70 feat: add AppDataProvider for centralized app data management and optimized refresh logic 2025-03-26 13:26:32 +08:00
wonfen
804fad6083 fix: reduce CPU usage caused by repeated refresh of "Current Proxy" card 2025-03-26 11:59:20 +08:00
Tunglies
98d3a48710 refactor: replace println with logging in core validation and tray quit function 2025-03-26 04:31:38 +08:00
Tunglies
0ec4f46052 chore: better install service prompt translation 2025-03-26 03:44:30 +08:00
Tunglies
a891341e35 fix: alpha version 2025-03-26 03:03:50 +08:00
Tunglies
10426af3ad chore: update UPDATELOG 2025-03-26 01:57:44 +08:00
Tunglies
5be1d604ee chore: fix release-alpha-version 2025-03-26 01:54:45 +08:00
Tunglies
1baa840160 v2.2.3-alpha begin 2025-03-26 01:51:19 +08:00
Tunglies
14347f60d5 Refactor hotkey logging with structured logging macro 2025-03-26 01:18:28 +08:00
Tunglies
df5424d55e feat: add logging module and update running mode terminology 2025-03-25 23:05:09 +08:00
wonfen
12065330e1 Release 2.2.2 2025-03-25 14:11:40 +08:00
wonfen
e054ac67fb feat: improve mihomo core and service keep-alive and reinstallation logic 2025-03-25 06:41:00 +08:00
Tunglies
31a7750482 chore: rename updater alpha release json names 2025-03-25 01:54:44 +08:00
Tunglies
2e38307f65 chore: updater channel logic 2025-03-25 01:49:51 +08:00
Tunglies
47accdd2b1 Revert "chore: updater channel combined"
This reverts commit cb0146573f1d1c41c9b4d8f7dba26cdabad1d9ae.
2025-03-25 01:38:52 +08:00
Tunglies
cb0146573f chore: updater channel combined 2025-03-25 01:32:37 +08:00
wonfen
cf78bb3686 refactor: service reinstallation logic on detection failure 2025-03-25 00:51:38 +08:00
Tunglies
b5b5ae4e7b chore: remove update endpoints temporary 2025-03-25 00:28:28 +08:00
Tunglies
b99bc7fcd1 feat: add default update log resolution and improve error handling 2025-03-25 00:23:19 +08:00
Tunglies
f50fe9159d refactor: remove deprecated lightweight module and adjust macOS activation policy 2025-03-24 20:11:38 +08:00
Tunglies
09f6917638 fix: update lightweight mode implementation and fix MacOS dock icon visibility 2025-03-24 19:16:21 +08:00
Tunglies
e330d75a89 fix: correct spelling of "Lightweight" in tray menu 2025-03-24 18:54:39 +08:00
Skrepysh
4f0ce7458e Update russian translation (#3109) 2025-03-24 18:34:50 +08:00
wonfen
a2811c4803 chore: update required service version 2025-03-24 03:59:55 +08:00
Tunglies
1c233783a7 chore: alpha update cron 2025-03-23 23:04:40 +08:00
Tunglies
3e45cc4650 chore: update bug_report.yml 2025-03-23 22:56:42 +08:00
Tunglies
1a7c076e07 feat: add option to enable/disable tray icon display on MacOS 2025-03-23 16:37:27 +08:00
Tunglies
da4fddf150 chore: remove unused import 2025-03-23 14:28:28 +08:00
wonfen
970eb62aa6 perf: improve Clash mode switch responsiveness on home card 2025-03-23 04:54:18 +08:00
Tunglies
d669650758 feat: add lightweight mode entry and related hotkey support 2025-03-23 03:10:48 +08:00
wonfen
69347160e9 perf: simplify code logic and improve efficiency 2025-03-23 02:16:06 +08:00
wonfen
a345b54a77 chore: update required service version 2025-03-23 00:16:32 +08:00
Tunglies
8aabcd77a5 feat: enhance tray icon handling with caching and speed rate rendering 2025-03-22 23:00:45 +08:00
Tunglies
c30f54609d chore: update UPDATELOG 2025-03-22 17:23:10 +08:00
Tunglies
0830236a73 fix: update alpha workflow to correctly extract alpha logs from UPDATELOG.md 2025-03-22 17:21:45 +08:00
Tunglies
44f21444bb feat: update versioning in package.json, Cargo.toml, and tauri.conf.json to append '-alpha' 2025-03-22 17:19:11 +08:00
Tunglies
1d88d98ea1 feat: ensure Mihomo and Verge services are running before executing commands 2025-03-22 17:05:42 +08:00
wonfen
86f69fd574 feat: add singleton check after core startup in sidecar mode 2025-03-22 15:01:55 +08:00
wonfen
e21846a2ce chore: update preview img 2025-03-22 11:07:20 +08:00
wonfen
d5981ca94f fix: alpha workflow 2025-03-22 08:45:55 +08:00
wonfen
8c5eb3b550 chore: remove unused code of current proxy card 2025-03-22 06:25:10 +08:00
Tunglies
55dc416109 feat: add scripts for managing alpha versioning and update workflow 2025-03-22 05:11:32 +08:00
Tunglies
ec30b888d1 chore: update UPDATELOG 2025-03-22 04:53:27 +08:00
Tunglies
2a92755e65 fix: linux hanlding mihomo-core and verge-service communication 2025-03-22 04:51:38 +08:00
wonfen
6976ea3c09 perf: optimize proxy refresh mechanism for home page current proxy card 2025-03-22 04:34:19 +08:00
wonfen
b07ed2dbf5 fix: theme color on connection detail card
fix: home page clash info card proxy address
2025-03-22 04:26:28 +08:00
wonfen
2ab923da87 fix: port setting sync problem 2025-03-21 12:28:51 +08:00
wonfen
9799d4f747 chore: add missing i18n 2025-03-21 10:29:20 +08:00
wonfen
f739836891 refactor: auto-truncate long text on home profile card
fix: sync system proxy and TUN mode status indicators on home proxy mode card
2025-03-21 05:23:45 +08:00
Tunglies
a28887be8e fix: add libxslt1.1 dependency to Ubuntu installation in workflows 2025-03-20 23:36:51 +08:00
Tunglies
0f13691ae0 v2.2.1 2025-03-20 23:22:34 +08:00
Tunglies
ae72b83dbe v2.2.1-alpha.1 2025-03-20 23:20:07 +08:00
Tunglies
2e38404434 fix: homepage entry lightweight mode exiting Macos tray icon
fix: lightweight mode better handling and logging logic
2025-03-20 23:17:37 +08:00
Tunglies
11b8c8be45 chore: v2.2.1 2025-03-20 22:33:20 +08:00
Tunglies
a06597a3a6 fix: homepage proxy card handle direct mode 2025-03-20 21:51:12 +08:00
Tunglies
108840c4be feat: enhance alpha release workflow to fetch update logs and generate release notes 2025-03-20 19:42:04 +08:00
Tunglies
16c8672aeb fix: update workflow to delete old release assets instead of the release itself 2025-03-20 19:33:26 +08:00
Tunglies
167edcf8ef chore: update version to 2.2.1-alpha and add alpha update logs
refactor: simplify version change detection in alpha workflow

chore: alpha delete old release then release new one

fix: update alpha workflow to handle missing ALPHA_LOGS and improve release notes generation

fix: update job dependencies in alpha workflow to include delete_otld_release
2025-03-20 19:11:48 +08:00
Tunglies
d6dd89b674 fix: remove macOS application menu setup due to CMD+C/V/A issues 2025-03-20 18:14:18 +08:00
Tunglies
fac2ee6374 chore: bug report template remove os label 2025-03-20 16:39:29 +08:00
Tunglies
dd7876845a chore: UPDATELOG update 2025-03-20 15:49:00 +08:00
Tunglies
56e6139c2b fix: ensure main window title is set correctly on macOS 2025-03-20 15:43:59 +08:00
wonfen
04bdd48a2a release 2.2.0 2025-03-20 14:44:26 +08:00
Tunglies
5b47fe5b88 Revert "fix: update permission config with app icon and name"
This reverts commit 618ba52bca8c270caca6bc9269ce7455a87751af.
2025-03-20 14:17:41 +08:00
Tunglies
84a5cf6b89 feat(hotkey): macos support CMD+W to close window as default 2025-03-20 13:02:26 +08:00
wonfen
618ba52bca fix: update permission config with app icon and name 2025-03-20 12:43:22 +08:00
Tunglies
5c0cde517f fix: update system architecture retrieval method in PlatformSpecification 2025-03-20 06:20:19 +08:00
Tunglies
1b249564a3 fix: update service response check for correct status code and message 2025-03-20 06:18:14 +08:00
Tunglies
81b5501b0e feat: implement auto lightweight mode timer functionality
This commit implements the automatic lightweight mode feature with timer functionality:

- Rename configuration properties from auto_enter_lite_mode to enable_auto_light_weight_mode and auto_enter_lite_mode_delay to auto_light_weight_minutes for better clarity
- Add window event listeners to detect when window is closed or gets focus
- Implement timer system to automatically enter lightweight mode after configured time
- Remove exit_lightweight_mode function as it's no longer needed with the new implementation
- Update UI components to reflect the new property names
- Add logging for lightweight mode operations
- Initialize lightweight mode based on user configuration at startup

The feature now allows users to set a timer that will automatically enter lightweight mode
after closing the main window, which can be cancelled by focusing the window again.
2025-03-20 06:01:38 +08:00
Tunglies
91ccb3045c feat: implement lightweight mode functionality and update related settings 2025-03-20 03:23:14 +08:00
wonfen
e31f176c25 feat: lite mode settings 2025-03-20 01:44:43 +08:00
Tunglies
ad45485009 fix: hotkeys on windows crash 2025-03-19 18:41:26 +08:00
wonfen
25e5cf2ac2 chore: update deps 2025-03-19 11:07:57 +08:00
wonfen
bd58d935c6 feat: add up/down name to home traffic graph card 2025-03-19 10:38:21 +08:00
wonfen
da2705ff7d chore: change defaut start page to home 2025-03-19 05:48:20 +08:00
wonfen
61f019f194 fix: cannot detect service mode on home card 2025-03-19 05:25:38 +08:00
Tunglies
74e441df5b feat: add lite mode toggle to home page 2025-03-19 02:18:20 +08:00
Tunglies
772ecdd3b0 refactor: improve proxy retrieval and add window destruction method 2025-03-19 02:04:01 +08:00
Tunglies
baa535b609 feat: add macOS application menu integration 2025-03-18 18:40:53 +08:00
Tunglies
a2ff0a7e20 chore: remove tray icon configuration from webview JSON files 2025-03-18 15:31:23 +08:00
Tunglies
84732f9835 Revert "feat: add Rust installation step and configure alpha release details"
This reverts commit fe1227618acefba5b4788dac26ac278e2a1eb4e2.
2025-03-18 14:37:56 +08:00
wonfen
dd17bcb0d6 chore: add missing colorful svg for home and unlock menu 2025-03-18 10:37:00 +08:00
wonfen
cab8e613a6 refactor: revise data retrieval for homepage traffic stats 2025-03-18 09:05:44 +08:00
Tunglies
fe1227618a feat: add Rust installation step and configure alpha release details 2025-03-18 01:08:46 +08:00
wonfen
596c52de87 feat: persist graph data after page reload 2025-03-18 00:37:10 +08:00
wonfen
ba5d5e9f86 feat: limit max url lenght on home profile card 2025-03-18 00:18:26 +08:00
wonfen
530669d288 fix: auto launch 2025-03-17 13:51:52 +08:00
wonfen
70b0f9a03a fix: resolve Netflix detection error 2025-03-17 11:57:12 +08:00
wonfen
105de99d06 perf: optimize all home page components 2025-03-17 11:47:02 +08:00
wonfen
6239f81f36 feat: sync auto-start status 2025-03-17 09:48:44 +08:00
wonfen
697d200ffe chore: update i18n for unlock test 2025-03-17 07:45:49 +08:00
wonfen
16d5077f55 perf: optimize CPU and memory usage of homepage traffic chart 2025-03-16 14:34:29 +08:00
wonfen
e0e1a05448 fix: sync proxy node selection 2025-03-16 14:24:58 +08:00
wonfen
bcaafa67a3 feat: unlock test page 2025-03-16 12:15:35 +08:00
Tunglies
36142656a4 refactor(timer): improve timer management with robust error handling
This commit improves the timer management system with the following enhancements:

Replace Mutex with RwLock for better read concurrency in timer state
Add structured TimerTask type to store task metadata
Use atomic boolean for initialization flag instead of mutex
Implement comprehensive error handling with detailed logging
Add rollback capability when task operations fail
Reduce lock contention by generating task diffs outside locks
Add timing metrics for task execution
Improve code organization and documentation
2025-03-15 18:58:12 +08:00
Tunglies
d6a48deb5a refactor(config): use bitflags for tracking update operations
Replace multiple boolean variables with a bitflag approach for tracking
required update operations in the patch_verge function. This improves
code maintainability and potentially performance by:

1. Using a single integer variable with bit operations instead of multiple booleans
2. Defining clear flags as enum variants for better code readability
3. Simplifying flag checks with bitwise operations

The UpdateFlags enum provides a clear and type-safe way to represent
different types of updates needed when patching Verge configuration.
2025-03-15 18:42:57 +08:00
Tunglies
e98ce0c2ae Optimize hotkey management to reduce lock contention and improve performance
- Minimize mutex lock durations in update() by processing data outside critical sections
- Pre-allocate collections to avoid unnecessary reallocations
- Replace forEach-style loops with more efficient for loops
- Add defensive null checks when accessing app_handle
- Improve error handling with more robust Option unwrapping
- Enhance code readability with descriptive comments
2025-03-15 17:52:14 +08:00
Tunglies
8118fc754c structure: move out crate_mihomo_api 2025-03-15 14:47:02 +08:00
Tunglies
1ec7a0f23c refactor: update request method handling to use reqwest::Method enum
fix: duplicated checks tray menu
2025-03-15 13:23:17 +08:00
Tunglies
488e8ef1d5 fix: optimize speed rate update logic for small values 2025-03-14 22:40:56 +08:00
wonfen
1f99cee78b feat: home page 2025-03-14 13:31:34 +08:00
Tunglies
c25015ed54 Revert "feat: add trigger updater workflow to GitHub actions for release and alpha workflows"
This reverts commit aaefc5b479149a48740fbcc7c0e4a1370bf1d903.
2025-03-14 02:00:55 +08:00
Tunglies
aaefc5b479 feat: add trigger updater workflow to GitHub actions for release and alpha workflows 2025-03-14 01:31:19 +08:00
GKarbon
1c58816c73 chore: correct typo in src/locales/en.json (#2978)
This pull request corrects a typo in src/locales/en.json by replacing “than” with “then”, gives more accurate instructions.
2025-03-14 01:04:44 +08:00
Christine.
0fd99358aa fix: build failed due to vite unexpected output dir config. (#2981) 2025-03-14 00:57:17 +08:00
Tunglies
d4012bace9 chore: remove tray icon configuration from Linux app settings 2025-03-13 19:03:27 +08:00
Tunglies
af7660686d fix: increase request timeout to 60 seconds for better reliability 2025-03-13 15:16:54 +08:00
Tunglies
b57c6e408a chore: git hooks for linter and formatter 2025-03-13 12:51:20 +08:00
Mimi
124934b012 feat: additional macos tray event handling option for menu display (#2958) 2025-03-13 10:08:38 +08:00
MaqicXu
1bef6d085d refactor(timer): enhance timer initialization and task handling (#2956)
- Add initialization flag to prevent duplicate timer initialization
- Improve logging for better debugging and monitoring
- Refactor async task function with proper error handling
- Add more detailed log messages throughout the timer lifecycle
2025-03-12 22:36:25 +08:00
Christine.
c73927c5ba chore: Change default TUN stack from 'mixed' to 'gvisor' (#2967) 2025-03-12 21:45:14 +08:00
Tunglies
692deb6012 fix: windows unmatched tray 2025-03-12 13:55:11 +08:00
Tunglies
8ec499f631 fix: windows different tray icon display 2025-03-12 13:28:04 +08:00
Tunglies
2bcd653a56 feat: update systray creation to use TrayIconBuilder and pass app reference
fix: macos systray duplicated icon
2025-03-12 13:04:15 +08:00
Tunglies
0f10952979 feat: add alpha update endpoints to tauri configuration 2025-03-11 01:31:31 +08:00
Tunglies
58fa67100f feat: add support for alpha updates and enhance updater functionality
feat: improve release handling by adding creation logic for non-existent releases
2025-03-11 01:27:17 +08:00
Tunglies
8e294916c4 rm: label issues workflow 2025-03-10 01:01:12 +08:00
Tunglies
6877e0c95d 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
37a333a023 feat: add scheduled workflow and commit change check to alpha build 2025-03-09 17:42:13 +08:00
Tunglies
48f1da963a refactor: update MihomoManager to handle traffic WebSocket URL and authorization 2025-03-09 14:44:15 +08:00
wonfen
e1905aced4 feat: enhance latency test logging and error handling 2025-03-09 04:22:34 +08:00
wonfen
f18202a3a4 refactor: improve webSocket connection handling and error recovery 2025-03-09 04:22:01 +08:00
wonfen
c1a9de4d66 feat: add icon file content check to prevent saving failed downloads 2025-03-09 02:34:57 +08:00
Tunglies
18f86874ee refactor: migrate clash client info retrieval to MihomoManager 2025-03-09 00:40:16 +08:00
Tunglies
e6686e0b82 rm: clash-api dead code 2025-03-09 00:29:14 +08:00
Tunglies
4bf166986d refactor: improve request handling and response processing in MihomoManager 2025-03-09 00:27:12 +08:00
Tunglies
0f60d84f6c refactor: streamline clash delay test and improve API interactions 2025-03-09 00:04:48 +08:00
Tunglies
15e54df67c refactor: streamline clash mode handling and improve API interactions 2025-03-08 22:41:14 +08:00
wonfen
4cb6ad7736 feat: optimize icon cache download and DNS view styling 2025-03-08 13:31:20 +08:00
wonfen
e27a32395a refactor: restructure DNS setting logic 2025-03-08 11:25:00 +08:00
wonfen
eddcf209c1 refactor: refine DNS handling to follow config and merge settings 2025-03-08 03:34:25 +08:00
wonfen
10a151d411 fix: regex for first character matching 2025-03-07 14:07:10 +08:00
wonfen
54d5586a60 perf: faster app exit 2025-03-07 13:44:07 +08:00
TianHua Liu
30ca547e50 fix: Notice @ts-ignore (#2896)
thx
2025-03-07 12:46:30 +08:00
0XE
a1944d1a90 feat: Add x-data-grid component localization (#2925)
thx
2025-03-07 12:45:56 +08:00
wonfen
c2b35fdaa5 test: remove entitlements.plist items 2025-03-07 06:49:24 +08:00
Tunglies
805b54d81e 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
e3579dac65 feat: enable dns settings by default 2025-03-06 14:40:35 +08:00
wonfen
f80591242e feat: add dns settings 2025-03-06 14:30:43 +08:00
Christine.
69cb9769c1 add: missing i18n text. (#2917) 2025-03-05 22:28:28 +08:00
0XE
efd42d9da0 fix: ISSUES #2727 (#2913) 2025-03-05 14:20:07 +08:00
Christine.
21a6340095 workflow: remove renaming behavior. (#2909) 2025-03-05 11:41:15 +08:00
Tunglies
6c96724dce feat(mihomo): refactor MihomoManager for global access and improve proxy retrieval (#2906) 2025-03-05 10:58:54 +08:00
wonfen
ebb194d2a2 feat: add admin permission prompt for system service 2025-03-05 10:22:57 +08:00
Tunglies
1a51a92b70 test: crate_mihomo_api additional headers 2025-03-05 08:14:37 +08:00
Tunglies
5760f16272 fix: extern controler api secert with headers 2025-03-05 08:09:42 +08:00
Tunglies
4ed36f6223 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
7ea7ca1415 fix: issues #2838 (#2886) 2025-03-04 20:46:17 +08:00
Tunglies
1ee8786ab7 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
44ca513241 fix: sync system proxy status indicator with hotkey 2025-03-04 06:57:42 +08:00
wonfen
73310b466b fix: correct type declarations for getProxiesInner and getProxyProviders 2025-03-04 02:26:26 +08:00
Tunglies
1ba688727e 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
3b69465016 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
3e53ea7209 Revert "refactor: improve proxy group UI and spacing (#2835)"
This reverts commit 520c33557eb37fcdc8ea89f3aeb5740e2deb0c7a.
2025-03-03 14:47:05 +08:00
wonfen
07bdc108ed feat: show service mode installation prompts in user mode 2025-03-03 14:42:31 +08:00
Tunglies
a18efb0e71 fix: speed format runns by docs 2025-03-03 11:36:21 +08:00
Tunglies
de1c825ad3 Revert "style: update box styling in settings page for improved layout (#2857)"
This reverts commit de2cff824e5502420dc7ff32f901af0a7a0fd191.
2025-03-03 08:00:10 +08:00
Tunglies
de2cff824e style: update box styling in settings page for improved layout (#2857) 2025-03-03 06:38:32 +08:00
Tunglies
aff504bddc feat: add export diagnostic info functionality (#2856) 2025-03-03 05:58:12 +08:00
wonfen
277390e597 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
fdcefe458e fix: windows/linux runtime crash 2025-03-03 02:27:45 +08:00
Christine.
9bb2160abe 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
97d683541d Revert "chore: alpha ci should remove old builds"
This reverts commit 9f7ffb80e1b74fdb91b92f5d56f0bad399948f76.
2025-03-03 00:16:17 +08:00
Tunglies
9f7ffb80e1 chore: alpha ci should remove old builds 2025-03-02 23:46:20 +08:00
Tunglies
c957ea7b24 feat: fish env export support 2025-03-02 23:20:10 +08:00
Tunglies
181fce16b1 version: 2.1.3 a 2025-03-02 20:46:53 +08:00
Tunglies
825f023505 version: 2.1.3 alpha 2025-03-02 20:43:54 +08:00
Tunglies
347ea53b32 version: 2.1.3 alpha (#2851)
* version: 2.1.3 alpha
2025-03-02 19:08:27 +08:00
Tunglies
d525e0dd70 chore: automatically label issues (#2844) 2025-03-02 18:50:31 +08:00
Tunglies
365e844b83 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
44bdeb555a 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
520c33557e 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
bfad5ac091 fix: macos frameless title 2025-03-02 04:30:58 +08:00
wonfen
3ecf4bc238 feat: refactor logging system into a global service 2025-03-02 04:20:38 +08:00
wonfen
028e4012aa fix: remove macos window title 2025-03-02 04:09:50 +08:00
wonfen
dc6d429b9c fix: add bottom padding to prevent jitter 2025-03-02 04:08:13 +08:00
Tunglies
625cf1a803 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
9ee011514a 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
184fd4a1ba 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.
23dcfd9401 fix: build failed with Windows (#2825) 2025-03-01 19:52:42 +08:00
Tunglies
1cdba297fb add: issues type template 2025-03-01 19:12:12 +08:00
Tunglies
1ed6743bbb 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
41c42bba32 fix: macos dock display icon and text (#2818) 2025-03-01 02:29:40 -08:00
wonfen
8fb66ea32c perf: improve scrolling performance and interaction in proxy group list 2025-03-01 08:31:31 +08:00
wonfen
51d4c1c4a5 fix: v2 action file rename 2025-03-01 08:04:31 +08:00
wonfen
86e069994e chore: updatelog 2025-03-01 03:45:03 +08:00
wonfen
1cb923b6d8 feat: add exit status check in core config validation 2025-03-01 03:39:13 +08:00
wonfen
b1d003b073 Release - 2.1.2 真·臻 2025-03-01 01:49:01 +08:00
Christine.
0fb4254481 add: i18n text for settings page. (#2815) 2025-03-01 01:36:36 +08:00
Tunglies
ae7f456011 feat: better setting UI layout (#2814) 2025-03-01 01:29:23 +08:00
wonfen
cd4bec6bfd chore: v2 updater 2025-03-01 01:29:23 +08:00
Tunglies
5906e0126d feat: better setting layout ui (#2809) 2025-03-01 01:29:23 +08:00
Tunglies
dce1395af1 feat: quiting when enable tun mode no more blocking system network (#2805) 2025-03-01 01:29:23 +08:00
wonfen
e7db13af37 test updater 2025-03-01 01:29:23 +08:00
wonfen
2eee8cd7d3 chore: updater v2 Capabilities 2025-02-28 10:55:53 +08:00
wonfen
836a2abae1 fix: updater 2025-02-28 09:33:50 +08:00
wonfen
a68a86d6db chore: updatelog 2025-02-28 07:34:56 +08:00
wonfen
ee9f0990fd feat: add ability to check service version and auto-reinstall 2025-02-28 06:45:30 +08:00
wonfen
59d0629e3f feat: enhance updater script with comprehensive platform support and logging 2025-02-27 17:19:04 +08:00
Christine.
17af292761 fix: connection details (#2778) 2025-02-27 14:53:17 +08:00
wonfen
a4dd4bcc8a feat: enhance merge config validation and error handling 2025-02-27 14:49:55 +08:00
wonfen
1a9b0a476b style: UI tweak 2025-02-27 13:04:46 +08:00
wonfen
bb015506e7 Release - 2.1.1 臻fix 2025-02-27 03:18:23 +08:00
Tunglies
76be5d8469 feat: macos display colorful icon with speed rate (#2771) 2025-02-27 01:51:52 +08:00
wonfen
1258e187f5 feat: improve file type detection for better script recognition 2025-02-26 15:59:19 +08:00
wonfen
4056a4c35f chore: downgrade Tauri updater plugin and add i18n for core switching 2025-02-26 15:04:47 +08:00
wonfen
b6677f0f72 feat: optimize hotkey behavior and window management logic 2025-02-26 11:03:50 +08:00
wonfen
e8c1e6f241 fix: CI 2025-02-26 09:52:15 +08:00
wonfen
618595ac4c Release 2.1.0 - 臻 2025-02-26 08:36:02 +08:00
wonfen
d54ba48c11 feat: enhance script validation and error handling 2025-02-26 05:21:14 +08:00
wonfen
a489012a0c feat: Add window state monitoring and auto-save in real-time 2025-02-26 00:36:02 +08:00
wonfen
a5acdc04e3 perf: Improve config validation error messages and handling 2025-02-25 13:47:29 +08:00
wonfen
709a20ed7b chore: revise translation 2025-02-24 23:26:04 +08:00
wonfen
c88f2099c1 chore: Change default TUN stack from 'mixed' to 'gvisor' 2025-02-24 23:10:09 +08:00
Christine.
f72a2a943b add: i18n text for config check (#2750) 2025-02-24 22:23:42 +08:00
wonfen
c51199719d fix: remove node related group info when deleting a node 2025-02-24 11:40:28 +08:00
wonfen
bf374f2e85 fix: menu switching issue 2025-02-24 11:37:23 +08:00
wonfen
34f450fcdb feat: Improve core change configuration validation and error handling 2025-02-24 07:34:03 +08:00
wonfen
23f75598e5 feat: Enhance configuration validation and error handling during app startup 2025-02-24 06:21:32 +08:00
zhaoyuan
afc238d60e feat: 通过添加CLASH_VERGE_REV_IP环境变量的方式,修改复制环境变量按钮的IP (#2734)
Co-authored-by: zymouse <zymouse@pixmoving.net>
2025-02-24 03:42:40 +08:00
wonfen
1291c38d58 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.
16caccde51 feat: auto rename alpha version (#2740) 2025-02-23 02:30:15 +08:00
wonfen
33f199fcd2 chore: Disable automatic alpha tag update in workflow 2025-02-20 14:25:58 +08:00
wonfen
2b534e0d51 refactor: Optimize proxy rendering and layout calculation 2025-02-20 14:21:55 +08:00
wonfen
23d1d210c7 refactor: Simplify tray icon event handling across platforms 2025-02-20 07:03:28 +08:00
wonfen
39a1d6202a chore: Update Node.js version and remove updater file generation 2025-02-20 02:55:47 +08:00
wonfen
f00a5af6c9 feat: Add system proxy status indicators 2025-02-19 13:56:22 +08:00
wonfen
48d68f5766 refactor: Simplify log data management and improve search functionality 2025-02-19 13:06:15 +08:00
wonfen
f948da748e fix: connections page traffic calculation 2025-02-19 02:24:21 +08:00
Tunglies
8b25d45109 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
c4ddc35746 style: tweak 2025-02-18 11:40:41 +08:00
wonfen
0122e2bdcf feat: Add persistent column width settings for connection table & process filtering 2025-02-18 11:04:22 +08:00
wonfen
94b75f463b refactor: Remove unused routing select component from settings page 2025-02-18 09:04:23 +08:00
wonfen
3c2e04290c feat: Improve letter item hover interaction with debounce mechanism 2025-02-18 09:00:03 +08:00
wonfen
f0331ec2d9 feat: Add pause/resume functionality for connection tracking 2025-02-18 08:14:09 +08:00
wonfen
3b4013a1b0 fix: resolve deprecated warnings in console 2025-02-18 07:10:28 +08:00
wonfen
31ddccd3e1 perf: Improve proxy list interaction and UI responsiveness 2025-02-18 05:54:16 +08:00
wonfen
d29fe4cb6c feat: Enhance alphabet selector with dynamic tooltip and scrolling 2025-02-18 01:55:44 +08:00
wonfen
6763537f22 refactor: Improve proxy data update mechanism with optimistic UI and error handling 2025-02-18 00:37:55 +08:00
wonfen
8ab4bd6293 fix: Refine TypeScript types for proxy groups component 2025-02-17 16:27:06 +08:00
wonfen
31bc644763 feat: Enhance proxy groups with Initials navigation and performance optimizations 2025-02-17 16:07:46 +08:00
wonfen
fcd672abeb feat: Optimize tray speed rate rendering and update logic 2025-02-17 15:08:19 +08:00
wonfen
5f550da0bb feat: Improve Virtuoso list rendering for proxy groups 2025-02-17 14:30:21 +08:00
wonfen
e865a86eef feat: Add persistent scroll position for proxy groups 2025-02-17 14:24:33 +08:00
wonfen
80ee2e4289 fix: alpha build win webview pack rename issue 2025-02-17 12:06:57 +08:00
wonfen
4d327594d3 chore: Update updater artifacts configuration 2025-02-17 11:22:14 +08:00
Akioe Yu
3363c37457 fix: check script error (#2453)
(cherry picked from commit 97bde64fd6516019a190d52f05af54ade623d57c)
2025-02-17 03:10:19 +08:00
wonfen
cee9be81bf chore: Add macOS-specific test for format_bytes_speed function 2025-02-17 03:10:03 +08:00
wonfen
932d36462f Revert "perf: Improve kernel management logic & add more dev mode logs"
This reverts commit ff2cf30238bcb5211f9b78e5fe65531e359d8d89.
2025-02-15 05:51:46 +08:00
Christine.
75c930f7ef add: i18n text for lightweight mode (#2692) 2025-02-13 14:27:12 +08:00
Tunglies
bdb178d893 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
3b0635e8a1 feat: add lightweight mode 2025-02-13 02:18:17 +08:00
wonfen
f5760784bf fix(macos): add missing required dependencies 2025-02-12 15:06:42 +08:00
wonfen
67f3554095 fix: remove unused imports to resolve compile warnings 2025-02-12 14:35:49 +08:00
wonfen
3bb3872e38 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
c98330ea1f fix(tray): resolve blank icon issue on Windows and optimize creation logic 2025-02-12 13:56:33 +08:00
Christine.
d895b68f04 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
e230981ac4 refactor: Improve tray icon and event handling across platforms & unify click behavior 2025-02-11 14:48:31 +08:00
wonfen
20763a741a fix: app crash without a default global_hotkey vaule 2025-02-11 01:50:48 +08:00
wonfen
4babcd9442 fix: deprecated CLI warnings 2025-02-11 01:05:51 +08:00
wonfen
d75f36066a feat: Add independent alpha update channel 2025-02-11 00:53:44 +08:00
wonfen
26ca4670ad fix: try to fix linux CI updater problem 2025-02-11 00:53:44 +08:00
Christine.
c4d6c167a2 add: i18n text (#2675) 2025-02-10 00:29:43 -08:00
wonfen
e5af9541da perf: Optimize kernel shutdown speed & logic 2025-02-10 13:18:00 +08:00
wonfen
89d9f47191 fix: crash caused by global_hotkey 2025-02-10 12:39:07 +08:00
wonfen
ff2cf30238 perf: Improve kernel management logic & add more dev mode logs 2025-02-09 14:19:15 +08:00
Tunglies
ebe0899eb1 perf: imporve clash mode switching performance on the main window (#2667) 2025-02-09 07:45:46 +08:00
Tunglies
a3d0a38b1e feat: option to enable global hotkey (#2665) 2025-02-09 07:45:22 +08:00
Tunglies
63bd0c87b2 fix: duplicate checked tray menu when profile name are same (#2660) 2025-02-08 09:36:04 +08:00
wonfen
db593fb188 feat: Optimize UI layout and page hierarchy 2025-02-07 09:13:30 +08:00
wonfen
67ae10b593 perf: optimize node latency refresh rate for faster updates 2025-02-06 07:58:42 +08:00
wonfen
c8d91c9e14 chore: update deps 2025-02-05 14:19:43 +08:00
Tunglies
6c54f5e9b4 Feature: Switch Proxy Profile from Tray Menu (#2644) 2025-02-05 08:52:47 +08:00
wonfen
8749648d97 fix: restore hotkey functionality after silent startup 2025-02-02 11:37:10 +08:00
wonfen
0b75b5ef26 feat: allow users to customize enhanced-mode and fakeip-range in tun mode by wonfen 2025-02-02 05:12:16 +08:00
wonfen
fbcadd0493 style: refine tray speed display 2025-02-02 03:43:15 +08:00
wonfen
75bb7a4dd7 fix: windows rounded corners 2025-01-19 09:47:43 +08:00
wonfen
4604fe4841 add donation 2025-01-18 16:08:24 +08:00
wonfen
e8badb0c0f release 2.0.3 2025-01-16 03:30:07 +08:00
白铭骢 (Mingcong Bai)
8906a8f3c6 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
4e32990a5d chore: update 2025-01-14 12:56:08 +08:00
huzibaca
9f2583d1f2 revert: update deps 2025-01-14 12:41:52 +08:00
huzibaca
a9b3d8885d chore: avoid duplicate updates when tray rate is off 2025-01-14 11:48:43 +08:00
huzibaca
29ec4dc546 feat: maoos tray speed can be closed 2025-01-13 20:48:25 +08:00
huzibaca
d0d5204cbc fix: fix: try to fix the language pack issue(2) 2025-01-13 17:09:38 +08:00
huzibaca
3d84acd7ac fix: windows tray icon color not updated 2025-01-13 13:49:56 +08:00
huzibaca
5057221f59 chore: update husky/pre-commit 2025-01-13 13:03:46 +08:00
huzibaca
3ddfbc5d2f fix: tray tooltip not updating 2025-01-13 13:01:12 +08:00
huzibaca
362270e3ea chore: update 2025-01-13 12:41:07 +08:00
huzibaca
db91177e90 chore: update deps 2025-01-12 23:10:15 +08:00
huzibaca
d2f51ce509 fix: try to fix the language pack issue 2025-01-12 22:22:06 +08:00
huzibaca
146a66fb09 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
03305f03c1 fix: fixes #2502 2025-01-11 12:55:20 +08:00
lucidhz
82e76bc58e fix: put_configs response add detail error message (#2492)
感谢pr
2025-01-05 01:27:12 -08:00
huzibaca
e8ff6c785a fix: modify the external control access key, the tray rate display is abnormal 2025-01-02 15:27:28 +08:00
huzibaca
a8fafb469a chore: update deps 2025-01-02 15:02:07 +08:00
huzibaca
b9a220cb63 chore: update 2025-01-01 08:25:31 +08:00
huzibaca
0b44d40b39 feat: the tray displays the shortcut keys that have been set 2025-01-01 08:14:15 +08:00
huzibaca
6b349eda45 chore: update 2024-12-31 11:11:29 +08:00
huzibaca
ad335ba005 fix: unused code 2024-12-31 08:08:52 +08:00
huzibaca
04d766884a fix: syntax issues 2024-12-31 04:50:12 +08:00
huzibaca
44d1ec433d Merge branch 'feat-macos-spped-rate-icon' into dev 2024-12-31 04:43:54 +08:00
huzibaca
97864e8df3 feat: macos system tray addition rate display 2024-12-31 04:42:55 +08:00
blagodaren
3916293e8f fix: small refactor 2024-12-28 13:57:30 +03:00
blagodaren
a527177b67 fix: add more lang and lang fix for tray 2024-12-28 10:05:30 +03:00
blagodaren
9c027b10b2 fix: add system language sup 2024-12-28 08:12:46 +03:00
huzibaca
5da7086475 fix: fixes #2460 2024-12-28 06:05:09 +08:00
huzibaca
0006012ae7 chore:add macos entitlements.list 2024-12-27 04:38:34 +08:00
huzibaca
a3f46ec037 chore: update 2024-12-27 02:42:35 +08:00
huzibaca
bfea52f9dd chore: secret is empty and no parameters are passed 2024-12-25 02:15:06 +08:00
huzibaca
53334f05b8 chore: update 2024-12-24 06:03:23 +08:00
huzibaca
ba195c41b6 refactor: when updating the tray, the logic is split to improve performance. 2024-12-24 04:52:14 +08:00
huzibaca
cca2f1ce61 chore: after saving the configuration file, restart the core 2024-12-24 02:22:46 +08:00
huzibaca
a51191c661 chore: update default_bypass
1. add 172.29.0.0/16
2024-12-23 06:06:46 +08:00
huzibaca
4cdb5f93b9 fix: turn off window shadow fixer, fixes #2425 2024-12-23 04:36:41 +08:00
huzibaca
fae658c9c2 chore: update 2024-12-15 01:14:16 +08:00
huzibaca
886a469634 fix: fixer #2346 2024-12-14 17:30:58 +08:00
huzibaca
c3c1394e86 chore: update deps 2024-12-14 16:21:16 +08:00
Langning Chen
6a00255fff 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
4f0aae0879 chorea: update deps 2024-12-10 22:36:13 +08:00
huzibaca
4f4fe4c41c feat: improve system bypass settings 2024-12-10 14:37:11 +08:00
huzibaca
2a4a3c8250 chore: enable default proxy bypass and hide custom bypass settings 2024-12-10 13:59:13 +08:00
huzibaca
7864acbadb chore: update deps(tauri-plugin-autostart) 2024-12-08 22:10:15 +08:00
huzibaca
ba18e64be0 chore: deeplink uses the latest API 2024-12-08 15:54:46 +08:00
huzibaca
ba8c1e5eb2 chore: update deps 2024-12-08 09:58:59 +08:00
huzibaca
2737fb2d87 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
899285735f chore: change fakeip range 2024-12-06 15:34:41 +08:00
huzibaca
f561d12d35 chore:set the sysproxy_rs version to a fixed version 2024-12-06 15:03:04 +08:00
huzibaca
be258b13e0 fix: extension script dns is overwritten fixer #2235 2024-12-06 12:35:09 +08:00
Myles Mo
dbce6b5f1a feat: add nullshell env variable (#2285) 2024-12-04 19:01:13 +08:00
huzibaca
30f0c99a58 fix: shift hotkey conversion fixer #2278 2024-12-04 13:19:56 +08:00
huzibaca
49880c05d9 chore: update version 2024-12-04 00:30:46 +08:00
huzibaca
dc2fc84f58 fix: set fontLigatures to false fixer #2267 2024-12-04 00:28:33 +08:00
lollapalooza
78c2a1694f feat: add bypass check feature (#2272) 2024-12-03 16:18:07 +08:00
Christine.
baf34dd0d3 fix: #2126 (#2233) 2024-12-03 16:03:21 +08:00
wonfen
48f9dede7b release 2.0.2 2024-12-01 11:00:09 +08:00
huzibaca
a1f2a621ef fix: resource file initialization failed 2024-12-01 09:38:29 +08:00
huzibaca
a1e8ddb461 fix: resolve service permission failed(2) 2024-11-30 10:45:53 +08:00
huzibaca
d33d90a36e chore: update 2024-11-30 10:38:15 +08:00
huzibaca
c3114b876f fix: resolve service permission failed 2024-11-30 10:29:16 +08:00
huzibaca
50285aebde feat: user uploaded icons can use templates, provided they are monochrome icons fixer #2213 2024-11-30 09:49:43 +08:00
huzibaca
0eb5ee6ea8 chore: update 2024-11-30 07:04:09 +08:00
huzibaca
16c9c95e19 refactor: update_core_config, simplify logic and delete invalid notifications 2024-11-30 05:43:59 +08:00
yyhhyy
3bc4da3e85 🐛 fix: Dynamically set IPv6 DNS configuration based on existing config (#2198)
thanks
2024-11-30 00:55:15 +08:00
wonfen
a028a2e1cc chore: change mac tun dns 2024-11-29 11:10:41 +08:00
huzibaca
9675a35dff chore: update 2024-11-29 03:46:37 +08:00
huzibaca
c1546fdd64 chore: remove debug code 2024-11-28 14:44:13 +08:00
Yu-Haifeng
a109efc1d6 fix: macOS tray icon not changed when use system proxy or tun mode (#2176) 2024-11-28 14:42:05 +08:00
huzibaca
0782b25830 fix: the deadlock caused by incorrect call of window_state due to document error 2024-11-28 12:54:55 +08:00
huzibaca
0041ff13b8 chore: clean startup registry keys for older versions(2) 2024-11-28 06:40:44 +08:00
huzibaca
4693a25aa0 chore: clean startup registry keys for older versions 2024-11-28 05:34:56 +08:00
wonfen
609df5b4a6 chore: set tun default stack 2024-11-28 04:22:13 +08:00
huzibaca
6df8140cb1 chore: update 2024-11-27 14:27:16 +08:00
wonfen
65b4cb3191 release 2.0.2 2024-11-27 12:41:51 +08:00
huzibaca
e1de481349 chore: update 2024-11-27 11:34:52 +08:00
huzibaca
6e1cc80b91 fix: when tun is closed, the full profile configuration is not restored 2024-11-27 10:35:42 +08:00
huzibaca
b658ce7e75 refactor: backup implementation
1. timeouts can be set for different operations
2. performance optimization
2024-11-27 07:34:34 +08:00
huzibaca
f91f374dfa chore: update 2024-11-27 07:09:03 +08:00
huzibaca
56f6de5410 fix: linux build failed 2024-11-27 05:54:48 +08:00
huzibaca
94d22ecfc3 fix: fixer #2158 2024-11-27 05:47:08 +08:00
huzibaca
e25d71c6c8 chore: update version 2024-11-27 05:09:46 +08:00
huzibaca
bb1b156d2f fix: build failed. #2156 2024-11-27 05:07:02 +08:00
huzibaca
1b80ddf1e9 chore: timeout adjusted to 15 seconds 2024-11-26 15:09:58 +08:00
huzibaca
66d2fe9074 fix: add scroll bar and scroll to top button to the test page . fixer #2118 2024-11-26 14:44:43 +08:00
wonfen
6e36910734 style: adjust 2024-11-26 12:11:20 +08:00
huzibaca
a553a33c46 chore: remove window title 2024-11-26 10:11:55 +08:00
huzibaca
2a6f8b401b fix: kernel-caused silent mode failure to start windows 2024-11-26 09:00:01 +08:00
huzibaca
fa30567140 chore: update 2024-11-26 05:46:34 +08:00
huzibaca
243f685b83 refactor: Implement using third-party libraries 2024-11-26 04:51:19 +08:00
huzibaca
6cf2373b34 chore: update 2024-11-26 03:30:15 +08:00
huzibaca
e842ea745a fix: file drag and drop import cannot be used 2024-11-26 03:07:25 +08:00
wonfen
9696c7cec0 style: Increased light color contrast to prevent blurring on some displays 2024-11-26 01:05:30 +08:00
huzibaca
c4986eec50 chore: replace mui grid with mui grid2 2024-11-25 12:51:13 +08:00
wonfen
61079e769e update change log 2024-11-25 06:46:29 +08:00
huzibaca
00d2c915d1 fix: Try to fix vcruntime duplicate installation 2024-11-25 06:33:47 +08:00
huzibaca
6ad975c420 fix: update failed(updater.install called before updater.download) 2024-11-25 05:27:26 +08:00
huzibaca
1cd1a2d907 fix: when restoring webdav, the current username, password and url are not preserved 2024-11-25 03:41:35 +08:00
huzibaca
39a3c3d3a7 fix: password failed due to character escaping 2024-11-25 02:48:56 +08:00
huzibaca
dfefcf03ad fix: remove comments from svg icons to prevent front-end crashes. fixer #2093 2024-11-25 01:58:02 +08:00
wonfen
825b00e618 fix: fakeip dns 2024-11-25 01:50:30 +08:00
huzibaca
ae562e1e92 chore: replace mui grid with mui grid2 2024-11-25 01:34:18 +08:00
huzibaca
21c7888595 fix: tun allocates the wrong private network segment, causing conflicts. 2024-11-25 01:06:51 +08:00
wonfen
3b87a4f9d0 release 2.0.1 2024-11-24 09:00:20 +08:00
huzibaca
8564a58eab fix: If an older version of the executable exists, delete it(2) 2024-11-24 08:25:46 +08:00
huzibaca
c5d009c2cd feat: try to use vscode first, if not found then use system default app 2024-11-24 08:20:00 +08:00
huzibaca
c2e165d825 fix: If an older version of the executable exists, delete it 2024-11-24 01:24:51 +08:00
huzibaca
922020c57a 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
8cdc33beab fix: windows cannot open yaml file 2024-11-23 19:46:53 +08:00
wonfen
23eafdfe00 fix: release workflow 2024-11-23 12:36:16 +08:00
wonfen
e72e8ea631 release 2.0.0 2024-11-23 11:34:17 +08:00
huzibaca
a610a43db0 chore: set the request timeout to 3 seconds 2024-11-23 06:50:28 +08:00
huzibaca
4a90ffe619 chore: remove notes 2024-11-23 06:47:43 +08:00
huzibaca
18e8357b6a chore: update 2024-11-23 06:42:05 +08:00
huzibaca
df39347b19 fix: restore the window state first, then set the window size 2024-11-23 06:39:25 +08:00
huzibaca
a36261d705 chore: update 2024-11-23 06:22:46 +08:00
huzibaca
f133d22124 chore: add debug codes & logs(2) 2024-11-23 06:20:13 +08:00
huzibaca
6ba276b43f chore: add debug codes & logs 2024-11-23 05:43:13 +08:00
huzibaca
44db98f260 chore: set the request timeout to 5 seconds 2024-11-22 14:15:31 +08:00
huzibaca
8873526619 feat: added scroll top button for agent and rule pages 2024-11-22 09:22:44 +08:00
huzibaca
37c2599754 chore: remove debug code 2024-11-22 09:08:25 +08:00
huzibaca
a079b470b8 fix: macOS DNS restore failed 2024-11-22 03:09:39 +08:00
huzibaca
0f9ed02bf0 chore: hide DNS cache file 2024-11-22 02:42:55 +08:00
huzibaca
9aeb68205c fix: husky - install command is DEPRECATED 2024-11-21 12:06:52 +08:00
huzibaca
82b4cf259c chore: remove unused code 2024-11-21 11:44:19 +08:00
huzibaca
566fd3e88b chore: remove unused code 2024-11-21 11:24:19 +08:00
huzibaca
fbecf4f47b fix: password should not be trimmed 2024-11-21 11:14:40 +08:00
huzibaca
52899d4def chore: remove unused code 2024-11-21 11:13:11 +08:00
huzibaca
a89a828b35 fix: serde::json passing IVerge to the front end without deserialization 2024-11-21 06:01:56 +08:00
huzibaca
4d0dbdaced chore: update 2024-11-20 07:37:30 +08:00
huzibaca
8003f9902e chore: remove notes 2024-11-20 07:37:03 +08:00
huzibaca
15bd7324fe feat: encryption configuration properties 2024-11-20 07:27:42 +08:00
huzibaca
bb44fc51bd fix: auto launch does not worki 2024-11-20 03:52:19 +08:00
huzibaca
67a32e60c7 chore: update 2024-11-20 01:15:03 +08:00
huzibaca
960725777c fix: exit_app does not work 2024-11-20 01:04:55 +08:00
huzibaca
98c6e0311b fix: windows cannot save window state(2) 2024-11-20 00:27:53 +08:00
huzibaca
95b7641f9c fix: windows cannot save window state 2024-11-19 23:32:32 +08:00
huzibaca
18b0c3f7aa chore: update deps 2024-11-19 13:35:57 +08:00
huzibaca
49d3644d6a chore: format 2024-11-19 13:28:17 +08:00
wonfen
ee9d12d933 feat: improve set dns logic 2024-11-19 11:38:23 +08:00
huzibaca
5d37015f4d chore: update web:build comman, use tsc --noEmit 2024-11-19 04:42:37 +08:00
huzibaca
dca25637c9 feat: add logger highlighting, support regular and case matching 2024-11-19 04:10:10 +08:00
huzibaca
a7020fd46c feat: add logger highlighting 2024-11-19 03:29:44 +08:00
huzibaca
1ef2b1aaf1 fix: log pause button not working 2024-11-19 02:56:58 +08:00
huzibaca
a7a661e60f feat: logger support all level filters 2024-11-19 01:40:45 +08:00
huzibaca
2a9e2d47f5 chore: update deps 2024-11-18 23:51:43 +08:00
huzibaca
e33b3043df chore: remove unused code 2024-11-18 23:48:02 +08:00
Christine.
3f41618aa1 fix: field error, #2044 (#2045) 2024-11-18 07:34:57 -08:00
huzibaca
a507d7567f refactor: remove useSWRSubscription and use useEffect 2024-11-18 16:41:17 +08:00
huzibaca
62a6f58705 chore: remove unused code 2024-11-18 08:16:31 +08:00
huzibaca
77dd074fc3 feat: Log level status is saved to local storage 2024-11-18 08:14:21 +08:00
huzibaca
e8c0051be3 refactor: use zustand store, rewrite log clearing logic 2024-11-18 06:48:23 +08:00
huzibaca
b3923eafc7 chore: typo warn -> warning 2024-11-18 06:01:51 +08:00
huzibaca
9ebd96611a refactor: logger fetch logic 2024-11-18 05:58:06 +08:00
huzibaca
824325a2eb fix: the CJS build of Vite's Node API is deprecated, part 2 2024-11-18 01:21:00 +08:00
huzibaca
e8b3bd5bdc fix: the CJS build of Vite's Node API is deprecated 2024-11-18 01:07:16 +08:00
Christine.
a59fda512c chore: Replace test URL to support iPv4&iPv6 (#2033) 2024-11-18 00:20:21 +08:00
huzibaca
ae181f6835 fix: webdav list interface compatibility issue 2024-11-17 23:57:28 +08:00
huzibaca
67bb242778 fix: webdav refreshes data and clears the original data when an error occurs. 2024-11-17 23:50:34 +08:00
huzibaca
2028c189aa chore: update 2024-11-17 01:01:36 +08:00
huzibaca
ba0dc4fb81 chore: update 2024-11-17 00:46:35 +08:00
huzibaca
c40db417d2 fix: fixes #1968 2024-11-16 23:09:10 +08:00
wonfen
0eb776cdd3 release rc.7 2024-11-16 10:15:50 +08:00
huzibaca
c79a7a7f6f chore: remove debug code 2024-11-16 07:35:34 +08:00
huzibaca
1e3c995e6a fix: fixes #1940 2024-11-16 06:08:49 +08:00
huzibaca
3f79e42628 fix: When the shortcut key closes the window, the window is minimized 2024-11-16 04:46:20 +08:00
huzibaca
c16ae89a3d fix: windows arm64 vsruntime is not installed 2024-11-16 03:12:11 +08:00
huzibaca
7132eaeb11 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
aef96f0d27 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
b20a56f1de chore: update 2024-11-15 17:19:14 +08:00
huzibaca
3073b4e48e chore: unified code format 2024-11-14 03:21:18 +08:00
huzibaca
7b53752ccd fix: call parameter error 2024-11-14 03:16:32 +08:00
wonfen
03eedf6175 chore: update info & i18n 2024-11-14 03:01:37 +08:00
huzibaca
2330a4bc93 chore: update dep(tauri-bubild) 2024-11-13 21:49:53 +08:00
huzibaca
36afae50b1 chore: update deps 2024-11-13 21:39:55 +08:00
huzibaca
272ee7577c feat: add refresh button 2024-11-13 00:53:52 +08:00
huzibaca
7f34073da6 fix: InputProps is deprecated 2024-11-13 00:30:30 +08:00
huzibaca
f46ee2a0a3 fix: mui grid has been deprecated 2024-11-13 00:21:22 +08:00
huzibaca
4a79f0c75d fix: application restart, window status not saved 2024-11-12 22:49:08 +08:00
huzibaca
27a78af269 fix: syntax issues caused by upgrading mui5 2024-11-12 20:05:28 +08:00
huzibaca
586af67829 chore: update 2024-11-12 19:44:57 +08:00
huzibaca
575d8c4240 fix: import error caused by dependency upgrade 2024-11-12 19:35:56 +08:00
huzibaca
d32734214b fix: undefined error 2024-11-12 19:34:53 +08:00
huzibaca
22ce5aab25 chore: update deps 2024-11-12 19:06:04 +08:00
wonfen
9f90a1c58e release rc.6 2024-11-12 03:49:21 +08:00
huzibaca
b5e0374946 feat: add webdav backup 2024-11-12 02:55:02 +08:00
huzibaca
44cb1c7f3e chore: update 2024-11-09 12:13:50 +08:00
huzibaca
80aba859e7 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
08360edd26 update: Readme 2024-11-09 09:00:53 +08:00
wonfen
6e69b3f032 fix: translation 2024-11-09 08:59:30 +08:00
huzibaca
19bb9c7f50 chore: update 2024-11-09 06:56:58 +08:00
huzibaca
f5dee51e9c chore: update 2024-11-09 05:50:51 +08:00
huzibaca
bd37fef720 chore: update 2024-11-08 23:42:00 +08:00
huzibaca
c22e4e5e2c chore: update 2024-11-08 21:46:15 +08:00
huzibaca
2887a2b6d3 Merge branch 'feat-add-unified-delay' into fix-migrate-tauri2-errors 2024-11-06 13:56:36 +08:00
huzibaca
01bde19701 chore: update 2024-11-06 13:52:36 +08:00
huzibaca
792f1826ee chore: update 2024-11-06 10:07:02 +08:00
huzibaca
c16795dce9 fix: @use rules must be written before any other rules. 2024-11-06 09:48:31 +08:00
huzibaca
c1597a0968 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
590aa950df chore: update 2024-11-05 21:40:02 +08:00
huzibaca
402018b95c chore: update 2024-11-05 21:35:17 +08:00
huzibaca
d5101ac2f3 chore: update 2024-11-05 18:38:50 +08:00
huzibaca
251942c91d chore: update 2024-11-05 18:30:28 +08:00
huzibaca
fe86b812cd chore: add tooltip 2024-11-05 17:39:59 +08:00
huzibaca
cb3bff589f feat: add unified delay 2024-11-05 16:24:58 +08:00
huzibaca
ec7d7ec559 fix: wrong window state save point 2024-11-04 09:53:40 +08:00
huzibaca
24c7a5b805 chore: update deps 2024-11-02 10:25:38 +08:00
huzibaca
0a4ecb1507 chore: update 2024-11-02 07:27:08 +08:00
huzibaca
d736dace50 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
70bbab909f chore: update 2024-11-01 06:03:01 +08:00
The1111mp
6625f78e4f 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
da2b4c8858 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
12df415dfd feat: use tauri_plugin_window_state 2024-10-30 18:52:53 +08:00
huzibaca
2493f463f3 revert: feat: use tauri-plugin-persisted-scope 2024-10-30 18:28:55 +08:00
huzibaca
f4238b1fb9 feat: use tauri-plugin-persisted-scope 2024-10-30 16:37:47 +08:00
huzibaca
794783ab4e chore: window hide replaces window minimize 2024-10-30 13:51:58 +08:00
wonfen
02634622a5 release: rc.5 2024-10-30 13:08:37 +08:00
wonfen
ac24501e76 chore: update bug report template 2024-10-30 12:59:19 +08:00
huzibaca
e40ea38112 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
b809b9bb80 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
73bad8f355 chore: restore linux custom close button 2024-10-30 06:30:49 +08:00
huzibaca
ac884da56b fix: performance issues caused by closing windows on mac 2024-10-30 06:27:29 +08:00
huzibaca
c35ab2e1cd chore: update 2024-10-29 10:15:07 +08:00
huzibaca
ed3907c273 chore: update 2024-10-29 10:13:53 +08:00
huzibaca
5e00287045 chore: disable websocket logging by default to reduce performance consumption. 2024-10-29 09:19:21 +08:00
huzibaca
95c6578911 fix incorrect usage of useCustomTheme 2024-10-29 09:07:08 +08:00
huzibaca
d22097ee33 chore: optimised the logic of dns processing 2024-10-28 13:09:55 +08:00
huzibaca
74251af163 fix: dns not restored when exiting the app 2024-10-28 00:24:57 +08:00
Christine.
7c1b11851f 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
f8724c4cb9 fix: unused variable 2024-10-27 10:19:42 +08:00
huzibaca
3baac034e5 chore: update 2024-10-27 06:47:16 +08:00
huzibaca
114f1426f3 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
7de63cea5c Revert "chore: update resource"
This reverts commit da907d0eeabfb825d9cce8fa95a1ade64efb805a.
2024-10-27 05:10:26 +08:00
huzibaca
34e3af2b38 chore: update 2024-10-27 05:06:42 +08:00
huzibaca
da907d0eea chore: update resource 2024-10-27 04:46:24 +08:00
huzibaca
9cbc2d9206 chore: update deps 2024-10-27 01:24:13 +08:00
huzibaca
db2e466d60 chore: update 2024-10-26 19:23:49 +08:00
wonfen
e5cdbf7361 release rc.4 2024-10-26 12:05:09 +08:00
huzibaca
a919f493d6 fix:public DNS not set in macos, tun+fake-ip 2024-10-26 08:55:00 +08:00
huzibaca
3660298683 chore: update 2024-10-26 01:44:06 +08:00
huzibaca
c845efe475 chore: update 2024-10-25 21:25:06 +08:00
huzibaca
38eb47132d chore: update 2024-10-25 14:13:30 +08:00
huzibaca
b1f097f32b chore: update 2024-10-25 14:12:27 +08:00
huzibaca
d3123253b3 fix: the tray icon does not display the correct colours on macos. 2024-10-25 13:58:22 +08:00
wonfen
7b1ec1ec22 chore: add mac sun ico 2024-10-25 12:27:22 +08:00
wonfen
4cefacfe73 release 2.0rc3 2024-10-24 12:52:20 +08:00
huzibaca
978acfa471 chore: update 2024-10-24 11:14:18 +08:00
huzibaca
fb2d138cbf Revert "chore: global mode doesn't show proxy groups, use placeholder images instead"
This reverts commit 0edd63edb575929e0802225b314e9c32174080f7.
2024-10-24 07:44:21 +08:00
huzibaca
0edd63edb5 chore: global mode doesn't show proxy groups, use placeholder images instead 2024-10-24 07:30:30 +08:00
huzibaca
97b730668c chore: update 2024-10-24 06:58:21 +08:00
huzibaca
26b8cf6d52 fix: proxy view display error 2024-10-24 06:54:27 +08:00
huzibaca
a979638368 refactor: the logic of profiles activation 2024-10-24 05:02:47 +08:00
huzibaca
97f434ad4a fix: restart app failure 2024-10-24 02:54:57 +08:00
huzibaca
34af040c48 fix: tun mode switch is not effective 2024-10-24 02:16:28 +08:00
huzibaca
cc81b443be refactor: IRuntime::patch_config() 2024-10-23 10:34:14 +08:00
huzibaca
d44f3c22c7 chore: update 2024-10-23 09:26:14 +08:00
huzibaca
3795b537f6 chore: update 2024-10-23 05:48:01 +08:00
huzibaca
ecb5f0885c fix: failed to install service on macos 2024-10-23 04:46:47 +08:00
huzibaca
86d2234713 feat: add windows uninstall script 2024-10-22 04:46:08 +08:00
huzibaca
62ddf26150 feat: add linux uninstall script 2024-10-22 03:17:04 +08:00
huzibaca
ec14b7c52f fix: debian executable file has no permissions 2024-10-22 02:37:36 +08:00
huzibaca
5f9cc38e82 chore: update 2024-10-22 01:34:26 +08:00
huzibaca
f48c58f299 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
c2843f3c4b fix: pac url error ,fixes #1889 2024-10-21 22:52:10 +08:00
wonfen
5d33df4e12 chore: update bug_report template 2024-10-21 11:30:07 +08:00
huzibaca
c030fb47ca 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
964daadb18 chore: update 2024-10-21 04:35:21 +08:00
wonfen
71a5698ac7 chore: update to 2.0 rc2 2024-10-21 03:15:02 +08:00
huzibaca
d41d74d0f8 chore: replace sudo with pkexec execution 2024-10-21 02:31:43 +08:00
huzibaca
f6c7a611a3 chore: update 2024-10-21 02:17:41 +08:00
huzibaca
06f4e79e5c chore: update 2024-10-21 01:35:28 +08:00
huzibaca
154cf44f0a chore: update 2024-10-21 01:30:13 +08:00
huzibaca
f6e2ff0e44 chore: update 2024-10-21 01:12:52 +08:00
huzibaca
2aba616f7f chore: update 2024-10-21 00:50:43 +08:00
huzibaca
95e21386b8 chore: update deps 2024-10-21 00:45:06 +08:00
huzibaca
250e908d9a chore: update 2024-10-21 00:34:28 +08:00
huzibaca
9742fb296c fix: failed to start system proxy with PAC mode 2024-10-20 23:13:23 +08:00
huzibaca
9ff3c2c0d4 chore: update deps 2024-10-20 22:35:16 +08:00
huzibaca
9dd7bd9530 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
6f477b7147 fix: mac commad+q global hijack is not released 2024-10-20 06:06:47 +08:00
huzibaca
8389826e30 chore: try to fix service not started on linux 2024-10-20 04:48:42 +08:00
huzibaca
aa31fb7470 chore: update 2024-10-18 22:50:03 +08:00
huzibaca
a013fe663c fix: try to fix service not started on linux 2024-10-18 07:08:34 +08:00
wonfen
89ce497431 chore: adjust translation 2024-10-17 05:09:00 +08:00
huzibaca
60c0b649e8 chore: update deps 2024-10-16 22:49:24 +08:00
huzibaca
2bbb5ea23b chore: update 2024-10-16 02:55:23 +08:00
huzibaca
4f9c1533c1 fix: system proxy cannot be closed on mac 2024-10-16 02:52:48 +08:00
huzibaca
a6a3847e30 chore: update 2024-10-16 01:55:22 +08:00
huzibaca
118f38dba3 chore : update version 2024-10-16 01:53:33 +08:00
huzibaca
879f946b28 chore: t emporarily cancel window closure and change to minimize 2024-10-16 01:53:16 +08:00
huzibaca
6acb8a5a91 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
a38f1e92e3 fix: path escape issue 2024-10-15 02:35:50 +08:00
wonfen
4ca977466e chore: update changelog and texts 2024-10-15 02:32:25 +08:00
huzibaca
ec45dc56fb chore: remove windows esc shortcut key 2024-10-14 13:45:26 +08:00
huzibaca
5686302653 chore: update 2024-10-13 03:01:32 +08:00
huzibaca
6322773513 chore: update deps 2024-10-13 02:55:35 +08:00
huzibaca
8e845fc919 chore: adjust tun default parameters 2024-10-13 02:42:37 +08:00
huzibaca
f90c8f2ae5 fox: external-controller cors error 2024-10-11 22:01:57 +08:00
huzibaca
b5af06529f chore: complete field identifier 2024-10-11 14:33:11 +08:00
huzibaca
fac3669f8e Merge branch 'main' into fix-migrate-tauri2-errors 2024-10-11 02:56:30 +08:00
Lvc Revincx
28ff8d6dcc fix: task bar icon misssing under linux wayland (#1816) 2024-10-11 02:52:57 +08:00
huzibaca
d0e7f6673c fix: installation service error 2024-10-11 00:59:34 +08:00
huzibaca
800dc21202 chore: update 2024-10-10 23:34:55 +08:00
huzibaca
f52089a674 chore:update 2024-10-10 18:52:20 +08:00
huzibaca
82543de95e chore: update 2024-10-10 18:40:39 +08:00
huzibaca
12db69407e chore: update 2024-10-10 02:21:22 +08:00
huzibaca
9b2b447b8b feat: Modify startup logic and install services by default 2024-10-10 00:34:36 +08:00
huzibaca
35f5e4ca41 chore: update 2024-10-09 01:14:03 +08:00
huzibaca
8a69713f6c refactor: core logic 2024-10-08 02:39:17 +08:00
huzibaca
efd8ef0380 chore: update 2024-10-06 02:03:32 +08:00
huzibaca
c5eacd1627 chore: revert 2024-10-06 01:09:59 +08:00
huzibaca
5fdb52d8d0 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
7869ce060f refactor: init_log 2024-10-05 12:19:44 +08:00
huzibaca
e0d96c0ce1 refactor: get_bypass func 2024-10-05 02:58:41 +08:00
huzibaca
30678904ee chore: update 2024-10-05 00:03:11 +08:00
huzibaca
953be61d89 chore: update deps 2024-10-04 23:56:50 +08:00
huzibaca
d8c85007d4 feat: add color to log 2024-10-04 22:38:06 +08:00
huzibaca
591c1cb454 chore: update 2024-10-04 05:37:53 +08:00
huzibaca
0ca90ed082 chore: update 2024-10-04 05:27:59 +08:00
huzibaca
4c963b3978 chore: update 2024-10-03 14:31:40 +08:00
huzibaca
071665f0c3 chore: update 2024-10-03 12:01:06 +08:00
huzibaca
9a7826752f feat: windows uses sysproxy.exe for system proxy 2024-10-03 02:09:22 +08:00
huzibaca
44b4187365 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
148807543f chore: replace sysproxy-rs repo 2024-10-02 17:43:14 +08:00
huzibaca
2b9fa09293 feat: test item, when icon is svg, add svg format check 2024-10-01 20:49:03 +08:00
huzibaca
10211d1d03 chore: optimize lock 2024-10-01 00:27:08 +08:00
huzibaca
46811f33ad chore: remove the manual release lock 2024-09-30 23:07:46 +08:00
wonfen
32b16790d3 chore: add service mode info 2024-09-30 01:04:15 +08:00
huzibaca
10592ca5a8 chore: update 2024-09-28 20:20:40 +08:00
huzibaca
dc5cb2e1b8 refactor: logic optimization 2024-09-28 12:37:01 +08:00
huzibaca
c10d782524 ,fix: when updating the verge configuration, notification error 2024-09-27 22:25:23 +08:00
wonfen
d73366984f chore: update bug report template 2024-09-27 14:08:50 +08:00
huzibaca
60b1e47ae6 chore: update 2024-09-27 10:05:31 +08:00
huzibaca
9591fb2c21 chore: remove compatibility code 2024-09-27 10:04:23 +08:00
huzibaca
c3cba03ac6 refactor: logic optimization 2024-09-27 00:24:05 +08:00
huzibaca
ce7818c436 chore: update 2024-09-26 19:47:25 +08:00
huzibaca
f367a81e44 chore: remove compatibility code 2024-09-26 19:47:00 +08:00
huzibaca
de507f7ec9 chore: update 2024-09-26 19:23:16 +08:00
huzibaca
0a8be603c8 chore: update 2024-09-26 12:20:57 +08:00
huzibaca
d7f033bd46 chore: update 2024-09-25 21:20:36 +08:00
huzibaca
1fb3b87697 chore: update 2024-09-25 21:07:01 +08:00
huzibaca
b9c8fa61b2 Revert "chore: cover panic error"
This reverts commit 0bacfa9286cc650a06fc4943d40114222f3d6aed.
2024-09-25 20:22:49 +08:00
huzibaca
99ea6d5080 chore: unified hotkey registration 2024-09-25 15:45:12 +08:00
huzibaca
0bacfa9286 chore: cover panic error 2024-09-25 15:44:05 +08:00
huzibaca
b350b605a8 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
d1eeeab7b1 chore: remove useless exit codes 2024-09-25 11:47:01 +08:00
huzibaca
54296ba84a fix: code lint 2024-09-24 20:11:33 +08:00
huzibaca
f82b0f259c fix: code lint 2024-09-24 20:09:30 +08:00
huzibaca
57f1c005e6 fix: code lint 2024-09-24 20:06:25 +08:00
huzibaca
d9e5387bff chore: add cross-env dev dependency 2024-09-24 16:35:19 +08:00
huzibaca
3154b8ce55 chore: update 2024-09-23 23:57:08 +08:00
huzibaca
45b48ede44 refactor: unify and simplify the call of app_handle(2) 2024-09-23 23:15:51 +08:00
huzibaca
961b86dcd2 chore: update 2024-09-23 17:15:28 +08:00
huzibaca
4d57c64b0d chore: update 2024-09-23 16:53:00 +08:00
huzibaca
1c894f3cfa refactor: unify and simplify the call of app_handle 2024-09-23 16:31:58 +08:00
huzibaca
3d6faecaed chore: update 2024-09-22 22:15:36 +08:00
huzibaca
2263ade187 chore: rename method 2024-09-22 20:29:43 +08:00
huzibaca
2cdf33d8a1 Revert "chore: update"
This reverts commit f6fce6bd317796983dcd5876dce7219bbcf78756.
2024-09-22 16:46:24 +08:00
huzibaca
f6fce6bd31 chore: update 2024-09-22 15:59:59 +08:00
huzibaca
f9f1721d66 chore: update 2024-09-22 15:39:52 +08:00
huzibaca
0792ac7de8 chore: update 2024-09-22 14:08:08 +08:00
huzibaca
98edb048b7 Revert "chore: remove useless exit codes and hooks"
This reverts commit d9671faca710ade167d10020466341fda10c1fa1.
2024-09-22 00:41:20 +08:00
huzibaca
52fcdf28fa fix: remove unused variable 2024-09-22 00:32:40 +08:00
huzibaca
d9671faca7 chore: remove useless exit codes and hooks 2024-09-21 21:05:34 +08:00
huzibaca
f456004543 chore: try adjusting the triggering of the tray mouse button 2024-09-21 15:38:14 +08:00
huzibaca
a38040d0ea chore : update 2024-09-20 18:02:19 +08:00
huzibaca
f18cd92318 fix: tray menu text was not aligned 2024-09-20 17:59:24 +08:00
huzibaca
06e1d0f8da chore: update 2024-09-20 17:49:31 +08:00
huzibaca
dffd663d7a Fix: Custom tray icon not working
1. Contains potential deadlock
2024-09-20 16:26:23 +08:00
huzibaca
414f9e9e96 fix: function parameter call error 2024-09-20 08:27:06 +08:00
Sukka
c17ea74856 chore: drop fs-extra (#1739)
nb!
2024-09-20 02:01:36 +08:00
huzibaca
9a08740e5b chore: update dev dependencis 2024-09-18 21:34:20 +08:00
huzibaca
8bea0db843 fix: exit hotkey conflict 2024-09-18 15:16:43 +08:00
huzibaca
2c612e371f fix: whether the window starts as fullscreen or not. 2024-09-18 13:23:27 +08:00
huzibaca
2f61dc9bc6 chore: revert 2024-09-18 12:24:52 +08:00
huzibaca
d18b78c11c refactor: exit app 2024-09-18 02:48:55 +08:00
huzibaca
95fb4f2e50 fix: the shortcut key to exit cannot be used 2024-09-18 01:41:46 +08:00
huzibaca
f31fe1440d fix: hotkey status not accurately processed, resulting in two triggers 2024-09-17 10:59:39 +08:00
huzibaca
10cd0a1f30 chore: update 2024-09-17 08:28:13 +08:00
huzibaca
659f854f62 chore: update 2024-09-17 08:02:29 +08:00
huzibaca
4a7f8dbe09 chore: add window to allow full screen configuration 2024-09-16 15:25:01 +08:00
huzibaca
3e19e574e6 chore: update 2024-09-16 07:08:22 +08:00
huzibaca
2af2d3664f chore: update 2024-09-16 06:43:25 +08:00
huzibaca
7dad46adb4 chore: update 2024-09-16 06:37:39 +08:00
huzibaca
b6fc6a751a chore: update 2024-09-16 06:17:22 +08:00
huzibaca
934674a8d7 chore: update 2024-09-16 05:50:05 +08:00
huzibaca
c66986f065 chore: modify application description definition 2024-09-16 05:38:52 +08:00
huzibaca
fc49e4a0da fox: try to restore the processing logic of windows custom scheme 2024-09-16 05:30:29 +08:00
huzibaca
b6e1d71b81 chore: update dev workflow 2024-09-15 07:57:34 +08:00
huzibaca
b3626a786d chore: add log 2024-09-15 07:03:04 +08:00
huzibaca
a398e28ac0 chore: follow official standards and adjust the main file 2024-09-15 06:24:53 +08:00
wonfen
892d4e597e chore: update change log and texts 2024-09-15 00:33:54 +08:00
huzibaca
e8311dd306 fix: when importing subscriptions, force the window to open 2024-09-14 10:03:19 +08:00
huzibaca
a0b266fef8 fix: after importing a subscription, it cannot be automatically switched to the current subscription. 2024-09-13 18:17:14 +08:00
huzibaca
983d1ea361 chore: update action(rust-toolchain) 2024-09-13 14:21:51 +08:00
huzibaca
db615b932c chore: update action(rust-toolchain) 2024-09-13 14:14:27 +08:00
wonfen
07415e512f chore: change folder name 2024-09-13 12:03:28 +08:00
huzibaca
0c6d417d8c feat: Added web notification of successful subscription import 2024-09-13 11:24:49 +08:00
huzibaca
772e01ad40 fix: when the service is not installed, the tray hides the Tun mode 2024-09-13 04:12:25 +08:00
huzibaca
1b2509d5bc refactor: url scheme implementation 2024-09-13 03:21:55 +08:00
huzibaca
fd963a8e66 chore: delete useless emebd server api 2024-09-12 19:01:08 +08:00
huzibaca
3c17fca369 fix:tray click event 2024-09-12 17:02:17 +08:00
huzibaca
4a76997044 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
894960ef5a feat: log panic 2024-09-12 07:59:51 +08:00
huzibaca
3c24d4bc4e chore: update deps 2024-09-12 06:14:23 +08:00
huzibaca
ed7e6a3495 fix: missing items in system tray 2024-09-12 05:45:15 +08:00
huzibaca
07de032e62 feat: migrate tauri 2.0 2024-09-11 08:15:03 +08:00
huzibaca
c07165531a chore: update 2024-09-04 07:53:16 +08:00
huzibaca
b1a22d4412 chore: update capabilities 2024-09-04 05:57:37 +08:00
wonfen
6734e5ef57 chore: 2.0.0 beta release 2024-09-03 00:00:12 +08:00
huzibaca
6cc81fe6b8 chore: update 2024-09-02 19:33:17 +08:00
wonfen
ad80d21e89 chore: use in-house update proxy 2024-08-24 02:02:17 +08:00
wonfen
97689c6cbb Style: fix macos new version btn position 2024-08-22 07:33:51 +08:00
wonfen
41c83dabde Release 1.7.7 2024-08-22 03:47:33 +08:00
wonfen
f909f0dcf9 chore: Shorten text to prevent Windows tooltip truncation. 2024-08-22 03:41:16 +08:00
wonfen
5c9bf30c79 fix: profiles will not be selected after import 2024-08-22 00:57:04 +08:00
wonfen
6efc518eed Release 1.7.6 2024-08-18 00:49:23 +08:00
tony-sung
198bd3a3dc Update web-ui-viewer.tsx Fix yacd parameter hostname error (#1484) 2024-08-18 00:23:24 +08:00
MystiPanda
7b1055702b feat: auto select profile 2024-07-30 09:06:12 +08:00
MystiPanda
d21bcce3c4 chore: prepend new added rules 2024-07-24 14:25:06 +08:00
MystiPanda
b5a26941ef fix: wrap the password in quotes
#1460
2024-07-24 14:22:23 +08:00
Avan
6ab7131378 style: AddressDisplay padding (#1457) 2024-07-23 22:17:51 +08:00
nhsmw
22bcc2e438 fix: update test url 2024-07-19 10:32:41 +08:00
MystiPanda
32edc0f1fe chore: remove unnecessary console log 2024-07-18 23:37:54 +08:00
MystiPanda
8faa0ce2c2 fix: use group testUrl
#1384
2024-07-18 15:04:55 +08:00
MystiPanda
bf05e5999b fix: windows x86 updater url 2024-07-18 14:38:31 +08:00
MystiPanda
3d7bdded31 fix: error command status 2024-07-16 18:37:56 +08:00
MystiPanda
7507182097 fix: MacOS service install error 2024-07-16 13:01:27 +08:00
MystiPanda
6853b3c531 fix(#1335): support cidr 2024-07-16 12:22:11 +08:00
MystiPanda
f2372a13e8 fix: try to fix install service 2024-07-16 12:07:23 +08:00
MystiPanda
3f321c8801 chore: disable okBtn 2024-07-15 23:38:49 +08:00
MystiPanda
8b47107df8 ci: fix winget uploader 2024-07-15 23:27:38 +08:00
MystiPanda
97e7136293 Release 1.7.5 2024-07-15 22:35:33 +08:00
MystiPanda
c8db58150e ci: fix winget uploader 2024-07-15 22:33:57 +08:00
MystiPanda
5e1067df59 fix: copy necessary files 2024-07-15 22:25:04 +08:00
MystiPanda
175444c59f Release 1.7.4 2024-07-15 20:56:18 +08:00
MystiPanda
b09d5ff3c9 fix: unified switch style 2024-07-15 20:42:00 +08:00
MystiPanda
43fc97137e feat: optimize the service mode interaction logic. 2024-07-15 20:02:05 +08:00
MystiPanda
c67eee57d6 Revert "fix: try to fix abnormal stuck"
This reverts commit 7edbae7b4c36a3b10e6e8400e43d7ef94d4495c7.
2024-07-14 12:49:06 +08:00
Sukka
607aa78059 fix(#1387): avoid catastrophic backtracking (#1396) 2024-07-14 12:26:02 +08:00
MystiPanda
7edbae7b4c fix: try to fix abnormal stuck
#1387
2024-07-13 21:41:04 +08:00
MystiPanda
232ff38084 chore: update locale 2024-07-13 19:15:07 +08:00
MystiPanda
d9d9ca67cd chore: unified icon style 2024-07-13 19:01:16 +08:00
MystiPanda
57fa48aef4 feat: display network interface 2024-07-13 14:15:13 +08:00
Avan
5a8e0749c2 feature: copy clash env (#1391)
* feat: copy clash env

* style: use ContentCopyRounded replace CopyAll
2024-07-13 01:03:46 +08:00
Avan
f834f069cd style: EditorViewer title align center (#1390) 2024-07-12 23:29:55 +08:00
MystiPanda
9b2dc10da2 fix: service install failed on macos 2024-07-12 20:54:31 +08:00
dongchengjie
c3730b7efd chore: checkbox items' title & button icons 2024-07-12 18:24:19 +08:00
dongchengjie
23499497a3 chore: editor resizing debounce 2024-07-08 23:18:06 +08:00
dongchengjie
3b78d609b7 chore: remove dialog overflow-scroll 2024-07-08 22:07:43 +08:00
dongchengjie
65529c3356 fix: editor fails to resize after toggling on macos 2024-07-08 21:40:56 +08:00
MystiPanda
899849d4dc fix dev.yml 2024-07-08 16:17:23 +08:00
MystiPanda
52393206e6 ci: add dev workflow 2024-07-08 13:20:36 +08:00
Akioe Yu
2cd1fa6601 fix: dnd box z-index (#1353)
* fix: dnd box z-index

* fix: dnd boxes
2024-07-08 10:49:57 +08:00
MystiPanda
e371bbedc0 perf: optimized response speed 2024-07-08 00:29:49 +08:00
dongchengjie
9dde385073 chore: group types locale 2024-07-07 21:56:20 +08:00
❤是纱雾酱哟~
afa3d39cb3 ci: Integrate "Winget Releaser" (#1326) 2024-07-07 20:42:17 +08:00
MystiPanda
fe41817f25 perf: change port too slow 2024-07-07 18:22:02 +08:00
MystiPanda
a865465514 feat: get network interface 2024-07-07 18:13:10 +08:00
dongchengjie
9278e74e9e fix: usage percent style 2024-07-07 18:08:02 +08:00
MystiPanda
689a1f739f fix: trojan uri parser 2024-07-07 11:47:49 +08:00
MystiPanda
19dee57b7e fix: tray icon size
#1341
2024-07-07 11:18:22 +08:00
MystiPanda
8690b91632 chore: disable browser autocomplete 2024-07-07 11:16:59 +08:00
MystiPanda
fa31cab11b Release 1.7.3 2024-07-06 18:48:25 +08:00
MystiPanda
46ee783f99 fix: bypass reg error
#1335
2024-07-06 18:23:23 +08:00
MystiPanda
199bba5da4 fix: limite cipher types 2024-07-06 10:49:42 +08:00
MystiPanda
16e8791472 fix: rule parser 2024-07-06 10:35:29 +08:00
MystiPanda
1728442d62 Revert "chore: add base64 decode step"
This reverts commit 4727d613c0d9279a6d9a447522cc1f455fc3e1ac.
2024-07-06 10:12:51 +08:00
MystiPanda
22f7f059ce chore: decode base64 2024-07-06 10:12:40 +08:00
MystiPanda
97629c1fc3 fix: some error 2024-07-06 09:54:16 +08:00
MystiPanda
4727d613c0 chore: add base64 decode step 2024-07-06 00:57:38 +08:00
MystiPanda
9ed138ea2b fix: style 2024-07-06 00:45:21 +08:00
MystiPanda
2cbd998941 fix: type 2024-07-06 00:23:52 +08:00
MystiPanda
806d70c243 chore: Improve URI parser 2024-07-05 22:44:05 +08:00
MystiPanda
74a1c7d489 feat: finish rpoxies editor 2024-07-05 19:49:32 +08:00
MystiPanda
f6ed5dc126 fix: groups config type error
feat(unfinished): add proxy editor
2024-07-05 00:38:50 +08:00
MystiPanda
e25185b9b8 feat: add profile name param for script 2024-07-04 23:11:54 +08:00
MystiPanda
5e6d8873b9 build: remove unused resource file 2024-07-04 22:34:48 +08:00
MystiPanda
951b48c337 chore: disable unnecessary ports by default 2024-07-04 18:58:34 +08:00
MystiPanda
7f209b76bf feat: support cache for groups editor 2024-07-04 18:53:39 +08:00
MystiPanda
890bfbe02d chore: unified style 2024-07-04 18:50:21 +08:00
MystiPanda
70f8c28ca6 fix: useseq error 2024-07-04 14:30:34 +08:00
MystiPanda
47bacdaed0 fix: locale typo 2024-07-04 14:18:21 +08:00
MystiPanda
bcd8eb2a09 feat: support visual edit for proxy group 2024-07-04 14:13:19 +08:00
MystiPanda
e4855d0143 fix: rules editor get groups error 2024-07-04 11:03:51 +08:00
MystiPanda
94f0ff1ed1 feat: support get merged rule-set name 2024-07-03 22:13:24 +08:00
dongchengjie
b5f0243a89 fix: remove rule condition where is not required 2024-07-03 17:03:12 +08:00
MystiPanda
17e59b8783 Release 1.7.2 2024-07-03 13:26:06 +08:00
MystiPanda
ffdf308b40 fix: Insertion order
#1300
2024-07-03 13:13:54 +08:00
MystiPanda
cdadc80945 fix: dialog styles 2024-07-03 13:13:29 +08:00
MystiPanda
294e1f5b10 fix: panic 2024-07-03 12:49:29 +08:00
MystiPanda
1c5eab6055 feat: support edit rules file 2024-07-03 12:37:08 +08:00
MystiPanda
f74f06e403 fix: edit groups error 2024-07-03 10:58:57 +08:00
MystiPanda
f04ee0baf2 Release 1.7.1 2024-07-03 09:52:19 +08:00
MystiPanda
689273fc24 fix: rules drag error 2024-07-03 09:45:14 +08:00
MystiPanda
6f4c59a15c fix: compatibility 2024-07-03 08:15:13 +08:00
dongchengjie
dc87097dfe fix: search-box takes no effect in rule-editor #1288 2024-07-03 03:08:01 +08:00
dongchengjie
24f4ab7597 fix: sub usage percent fails to display when number is too small #1290 2024-07-03 02:59:32 +08:00
MystiPanda
ad94f0a292 Release 1.7.0 2024-07-03 00:32:14 +08:00
MystiPanda
695613a063 perf: optimize performance of the rule editor 2024-07-02 23:55:29 +08:00
dongchengjie
6f1828eabc feat: editor add tool-tar buttons 2024-07-02 23:24:44 +08:00
dongchengjie
bf158b3bf0 refactor: editor-viewer using react-monaco-editor 2024-07-02 21:02:29 +08:00
dongchengjie
13618e6a0a chore: bump schema to 1.18.5-alpha7 2024-07-02 19:38:44 +08:00
MystiPanda
c424e9dec8 chore: disable autocomplete 2024-07-02 19:02:05 +08:00
MystiPanda
a2e9523707 fix: default value of global extend config 2024-07-02 18:46:23 +08:00
dongchengjie
606817ae06 chore: add rule list search-box 2024-07-02 17:32:49 +08:00
dongchengjie
7124d326fc chore: rule types locale 2024-07-02 17:04:22 +08:00
MystiPanda
f9f4653e33 chore: check the validity of the rule content 2024-07-02 13:11:54 +08:00
MystiPanda
bf8eebe537 chore: Adjust the chain processing execution order and default value 2024-07-02 12:40:28 +08:00
MystiPanda
bd9eef6502 build: update depends 2024-07-02 12:21:17 +08:00
MystiPanda
e343b1790e chore: update locale 2024-07-01 23:30:14 +08:00
MystiPanda
d81ef1d67c feat: allow set bypass without using default value 2024-07-01 22:53:32 +08:00
Sukka
6e374bcd4e ci: speed up cargo install by enabling cache (webview2) (#1283) 2024-07-01 14:23:21 +08:00
MystiPanda
fb4648d2af feat: global merge and script 2024-07-01 08:25:03 +08:00
MystiPanda
a63fc25f14 feat: drag to reorder rules 2024-06-30 23:16:21 +08:00
MystiPanda
5e20e9ae1c chore: select rule-set name 2024-06-30 22:46:11 +08:00
MystiPanda
7d5d604ea6 feat: display rules list 2024-06-30 22:34:26 +08:00
MystiPanda
a722581868 fix: editor init error 2024-06-30 18:05:38 +08:00
Sukka
28f3044bdd ci: speed up cargo install by enabling cache (#1279) 2024-06-30 17:21:55 +08:00
MystiPanda
497804434d feat: rules editor 2024-06-30 17:17:04 +08:00
MystiPanda
d2d6ee806d chore: locale 2024-06-30 13:03:36 +08:00
MystiPanda
2e106265f9 feat(unfinished): rules editor 2024-06-30 12:46:31 +08:00
MystiPanda
28fb0b433b refactor: pure merge 2024-06-30 07:58:44 +08:00
MystiPanda
171bd6b327 fix: delete logic 2024-06-30 07:37:52 +08:00
MystiPanda
198e215d54 chore: update locale 2024-06-30 07:18:10 +08:00
MystiPanda
4d424e70bc feat: support seq editor 2024-06-30 00:22:05 +08:00
MystiPanda
3efef52398 refactor: Associate Profile with Merge/Script. 2024-06-29 23:07:44 +08:00
MystiPanda
b85929772e refactor: use async instead of block_on 2024-06-29 19:02:37 +08:00
dongchengjie
041522f94e fix: do not reactive when changed profile is not current 2024-06-29 11:22:11 +08:00
dongchengjie
80d3c9e96f feat: reactive after save when profile content changes 2024-06-29 09:21:50 +08:00
dongchengjie
6ee5e560cc fix: #1261 2024-06-29 05:02:06 +08:00
wonfen
2be9eb4bae chore: add TG link & descriptions 2024-06-29 04:55:43 +08:00
dongchengjie
0a8935686a chore: add descriptions for Miscellaneous 2024-06-28 15:54:27 +08:00
MystiPanda
0109d9148b fix: unset dns when exit 2024-06-28 12:18:18 +08:00
MystiPanda
212518c682 feat: set dns by service 2024-06-28 11:45:44 +08:00
dongchengjie
59b4f1ebab chore: remove duplicate locales 2024-06-26 19:11:39 +08:00
dongchengjie
ee9462c221 fix: await compatibility in #c648dc6 2024-06-26 18:29:50 +08:00
dongchengjie
c648dc6c99 feat: support manual memory cleanup when running in debug mode 2024-06-26 17:44:42 +08:00
dongchengjie
4f1b8094a3 chore: cleanup 2024-06-26 08:24:43 +08:00
dongchengjie
c89ccf7185 refactor: extract tooltip icon as component 2024-06-26 08:10:18 +08:00
dongchengjie
753395965a chore: tooltips and locales 2024-06-26 05:33:06 +08:00
dongchengjie
04be747d52 chore: fix connection table bg 2024-06-23 20:40:09 +08:00
dongchengjie
f828ed3edf 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
b98d9c2932 feat: handle break change update 2024-06-22 21:05:46 +08:00
dongchengjie
aba2ce8390 chore: hotkeys display delimiter 2024-06-21 18:24:58 +08:00
MystiPanda
8bd8e149cf chore: hide delay for preset outbound 2024-06-21 00:16:41 +08:00
MystiPanda
e7c359a2e7 revert: just kill 2024-06-20 14:27:13 +08:00
MystiPanda
d64d25380a feat: custom dmg background 2024-06-20 13:50:28 +08:00
dongchengjie
6cba6166fb chore: fix table header bg in connections 2024-06-20 02:01:51 +08:00
dongchengjie
e66f5fe253 chore: disable shortcuts #1239 2024-06-20 01:39:51 +08:00
MystiPanda
1d4388d444 fix: runtime error 2024-06-19 11:10:40 +08:00
MystiPanda
28ab08a7ca refactor: change core binary name 2024-06-19 10:04:28 +08:00
MystiPanda
6fa0f92ceb refactor: kill core by process name 2024-06-18 12:36:58 +08:00
MystiPanda
3083ab74a6 fix: reg error 2024-06-17 16:38:30 +08:00
Sukka
b6ea73af83 fix(#1226): missing conn unsub (#1228) 2024-06-17 13:36:35 +08:00
Sukka
1fa3ffb1ff refactor(connections): use swr subscription (#1226) 2024-06-17 13:14:36 +08:00
dongchengjie
af89630095 fix: resizing reset when data changes 2024-06-17 13:03:47 +08:00
Sukka
18f0177fce refactor(log): use swr subscription (#1220) 2024-06-17 10:48:37 +08:00
MystiPanda
d89eecacba chore: update depends 2024-06-17 10:46:18 +08:00
Sukka
a9149fb92e 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
9a04208a11 Revert "feat: disable running with admin permission and check service mode"
This reverts commit 481e473b604da37e8b7cbe2c929dc12ba5b6c120.
2024-06-16 12:06:23 +08:00
Sukka
6cdf199531 ci(alpha): avoid race by cancel non-latest concurrent runs (#1213) 2024-06-16 01:10:30 +08:00
Sukka
44dc7fe24a fix: hide save button in readonly editor view (#1208) 2024-06-15 19:24:26 +08:00
Sukka
455892b414 fix(#1203): correct types (#1207) 2024-06-15 19:23:58 +08:00
Sukka
4f5227782a perf: replace Array#map Array#filter chain w/ Array#reduce (#1203) 2024-06-15 12:22:33 +08:00
MystiPanda
481e473b60 feat: disable running with admin permission and check service mode 2024-06-14 23:15:49 +08:00
MystiPanda
2c2a1f638b build: downgrade auto-launch 2024-06-14 21:26:30 +08:00
MystiPanda
973e269f46 fix: fix bypass check regex 2024-06-14 21:23:58 +08:00
Sukka
9f76e0e056 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
3a5f1b41a4 chore: update metacubexd #1200 2024-06-14 17:09:12 +08:00
dongchengjie
e2d8369daf feat: local profile name autofill #1191 2024-06-13 16:29:25 +08:00
MystiPanda
a20d4959bf refactor: remove grant logic 2024-06-13 16:07:56 +08:00
MystiPanda
7b887e4cdd fix: check service 2024-06-13 12:58:47 +08:00
MystiPanda
bc9cbd2993 ci: fix release 2024-06-12 12:25:12 +08:00
MystiPanda
9baa0e247f chore: update publisher 2024-06-12 11:51:44 +08:00
MystiPanda
2df8d2bc69 Release 1.6.6 2024-06-12 10:33:20 +08:00
MystiPanda
b8165fb06e fix: start param error 2024-06-12 10:19:23 +08:00
MystiPanda
c698b24e01 chore: update & fmt & clippy 2024-06-12 10:00:22 +08:00
dongchengjie
e70249cb2e chore: missing locale 2024-06-11 16:19:25 +08:00
dongchengjie
8a9bfe8281 chore: locale "Invalid Bypass Format" 2024-06-09 20:02:20 +08:00
MystiPanda
8c2a4e627e fix: start param error 2024-06-09 20:00:17 +08:00
MystiPanda
bf35c92c14 feat: check bypass format 2024-06-09 13:37:47 +08:00
MystiPanda
019293a034 feat: keep default bypass 2024-06-09 12:45:57 +08:00
dongchengjie
46dc40149e chore: missing locale 2024-06-09 11:16:13 +08:00
dongchengjie
444643eb6f chore: locale reorganization 2024-06-09 09:12:14 +08:00
Eric Huang
2913b911e3 feat(settings page): add loading state (#1157)
* feat(settings page): add loading state

* fix: type
2024-06-09 06:26:07 +08:00
wonfen
ca323371a7 chore: update ci with apple notarization 2024-06-09 02:01:33 +08:00
MystiPanda
a06cb39777 ci: fix error 2024-06-08 20:58:57 +08:00
MystiPanda
b0ec8767a2 ci: update 2024-06-08 20:45:28 +08:00
MystiPanda
353fb49a87 feat: add download button on updater dialog
#1129
2024-06-08 20:35:23 +08:00
MystiPanda
e453b40e0b fix: run app as normal user 2024-06-08 20:20:47 +08:00
MystiPanda
0c6f8ce77d build: update depends 2024-06-08 20:02:25 +08:00
MystiPanda
c0219662bb build: remove appimage 2024-06-08 19:36:58 +08:00
MystiPanda
aae71d375c ci: codesign 2024-06-08 18:52:34 +08:00
dongchengjie
b6228e4c59 chore: disbale Meta+Q on macOS 2024-06-08 13:12:24 +08:00
Sukka
3a9a1439d9 refactor: simplify useConnectionSetting (#1141) 2024-06-07 17:23:53 +08:00
wonfen
7cf256dc7c fix: apple sign error again 2024-06-07 17:19:44 +08:00
MystiPanda
c3c26998bf fix: sign error 2024-06-07 16:39:06 +08:00
MystiPanda
02e860480b fix: sign error 2024-06-07 16:37:58 +08:00
Eric Huang
7737b8b596 feat: make SettingItem clickable (#1138)
* feat: make `SettingItem` clickable

* clean up
2024-06-07 15:51:51 +08:00
Sukka
2725322fd5 refactor: replace recoil (#1137) 2024-06-07 12:27:37 +08:00
wonfen
6c6ccda6b3 Style: modify proxy pages 2024-06-07 10:32:27 +08:00
MystiPanda
d71269e223 fix: sign error 2024-06-06 16:06:58 +08:00
wonfen
36266d2b10 chore: apple developer sign test 2024-06-06 15:21:53 +08:00
dongchengjie
acae62de87 chore: test_delay trace 2024-06-05 19:17:43 +08:00
dongchengjie
9fe4197cae chore: theme colors to uppercase 2024-06-05 14:20:24 +08:00
dongchengjie
7fa1a8d54a build: no legacy chunks 2024-06-05 09:04:08 +08:00
Sukka
2333271c20 fix(#1126): add Object.hasOwn polyfill (#1127) 2024-06-05 00:12:06 +08:00
dongchengjie
5b83149567 fix: csp missing asset: 2024-06-04 15:26:39 +08:00
MystiPanda
250a35baab build: update depends 2024-06-04 12:16:42 +08:00
MystiPanda
d60ba95532 Release 1.6.5 2024-06-04 12:11:38 +08:00
MystiPanda
c901472198 fix: package error 2024-06-04 10:47:17 +08:00
MystiPanda
2a5b70fb13 feat: support rpm package 2024-06-04 10:16:25 +08:00
MystiPanda
dc6db6e4b3 build: use latest tauri 2024-06-04 10:09:04 +08:00
Zhenfu Shi
8df6f32314 chore: Enable Ad Hoc signing on macOS (#1117)
Closes #1116
2024-06-03 09:01:33 +08:00
wonfen
a2d8c894fe Revert "chore: change default test url"
This reverts commit a7cf968d04fe9abede0dc4478dc7c03edc224389.
2024-06-03 08:25:06 +08:00
dongchengjie
a1996768f1 chore: use icons files instead of hard coding 2024-06-03 07:21:40 +08:00
dongchengjie
205587cb9e fix: group selected be overwritten when saving test 2024-06-03 06:38:20 +08:00
dongchengjie
224b2ef952 chore: improve UI in connections page 2024-06-02 21:52:31 +08:00
aixiao0621
90a83dc753 fix: a display error on the connections page 2024-06-02 17:26:23 +08:00
MystiPanda
a7cf968d04 chore: change default test url 2024-06-01 23:14:47 +08:00
MystiPanda
80ff72bae1 build: update depends 2024-06-01 23:01:27 +08:00
dongchengjie
5320fc8111 refactor: polyfills review 2024-05-30 20:27:12 +08:00
dongchengjie
8753531e82 fix: research when search box mode changes 2024-05-30 10:45:24 +08:00
dongchengjie
03a845f2b3 chore: Content-Security-Policy 2024-05-29 10:20:56 +08:00
dongchengjie
25b05f127d ci: fix heap oom 2024-05-29 09:49:11 +08:00
dongchengjie
073beb0135 build: polyfills 2024-05-29 09:39:26 +08:00
dongchengjie
ef7659691b build: import babel 2024-05-28 09:59:41 +08:00
dongchengjie
e69c0c079e fix: try to fix #1084 2024-05-27 23:19:44 +08:00
dongchengjie
dc31269a06 typo: inconsistent style in layout-viewer 2024-05-27 16:37:48 +08:00
MystiPanda
df9eccabea Release 1.6.4 2024-05-26 22:07:53 +08:00
MystiPanda
7788f5ae4c Revert "refactor: use axios tauri adapter"
This reverts commit 8092e5c3a8d030649b00554353e21a761052fd3e.
2024-05-26 21:18:02 +08:00
dongchengjie
ff5456c178 chore: %mixed-port% hint for PAC script 2024-05-26 19:53:42 +08:00
MystiPanda
40ed702437 Release 1.6.3 2024-05-26 19:43:26 +08:00
MystiPanda
65924e9a5d chore: add content type 2024-05-26 19:26:57 +08:00
MystiPanda
a88d149dad fix: auto proxy changed by guard 2024-05-26 19:07:14 +08:00
MystiPanda
b9ec94d835 feat: Support PAC Mode 2024-05-26 17:59:39 +08:00
dongchengjie
fc1675575a chore: emoji display support in editor 2024-05-25 18:02:32 +08:00
farzadhallaji
2f7229720f Update fa.json (#1060) 2024-05-24 19:39:40 +08:00
MystiPanda
0187fc7b22 ci: use rust 1.77.0 2024-05-23 11:47:41 +08:00
dongchengjie
540e1a9650 fix: no snippets and warnings in runtime config editor 2024-05-21 06:18:09 +08:00
dongchengjie
a371cd1d79 chore: update locale in connection 2024-05-20 13:42:45 +08:00
dongchengjie
16b11fee31 chore: text overflow word-wrap & cleanup 2024-05-20 12:38:41 +08:00
MystiPanda
1bc46f22b4 build: use compatible 2024-05-19 19:30:53 +08:00
MystiPanda
554c8fe163 fix: installer package error 2024-05-19 19:13:21 +08:00
MystiPanda
8f6bf6e002 build: use latest core 2024-05-19 13:00:27 +08:00
dongchengjie
d5dd8e9346 chore: schema 1.18.5-alpha 2024-05-19 12:37:18 +08:00
dongchengjie
4a67e1021a chore: fix select component bg in connection 2024-05-18 18:42:51 +08:00
dongchengjie
c97061770a chore: fix search-box bg 2024-05-18 18:20:05 +08:00
MystiPanda
55b331511e chore: support more asset scope 2024-05-18 15:47:45 +08:00
oomeow
4b9b5e861f perf: memoize the proxy col items (#1029) 2024-05-18 15:14:22 +08:00
oomeow
b8599a0642 fix: clipboard doesn't work and set_shadow method is not supported in Linux (#1030) 2024-05-18 15:14:00 +08:00
dongchengjie
ae6530585a feat: editor import PAC definition 2024-05-18 14:59:17 +08:00
dongchengjie
39aa1fa2a4 chore: hint for canceling fixed #840 2024-05-17 20:44:18 +08:00
dongchengjie
4f740acabd chore: use search-box in logs and connections 2024-05-17 19:44:42 +08:00
dongchengjie
2cc9b91895 chore: component base-search-box 2024-05-17 19:13:33 +08:00
dongchengjie
4eedc39e97 chore: fix editor dialog scroll overflow 2024-05-17 04:03:49 +08:00
dongchengjie
b99e8d7f46 fix: missing locale 2024-05-15 23:27:31 +08:00
dongchengjie
a8a27aeadd chore: current bypass wrap 2024-05-15 22:59:53 +08:00
MystiPanda
21176d2fd3 feat: support monochrome tray icon 2024-05-15 14:50:10 +08:00
dongchengjie
224c65438f chore: logo svg 2024-05-14 16:53:39 +08:00
dongchengjie
f1c21b642f feat: doc reference link on settings header 2024-05-14 14:40:47 +08:00
dongchengjie
030d1f374a chore: use coding fonts in editor 2024-05-14 14:20:50 +08:00
dongchengjie
0b29fa2288 fix: switch missing break 2024-05-14 01:24:23 +08:00
dongchengjie
b721f148f0 chore: @ts-ignore schema check 2024-05-14 01:21:28 +08:00
dongchengjie
63434a2f87 chore: revert schema to beta4 2024-05-14 00:59:05 +08:00
dongchengjie
0d6f0e66be chore: palette locale 2024-05-13 23:12:29 +08:00
dongchengjie
fb3f1365c5 typo: unused import 2024-05-13 22:59:24 +08:00
dongchengjie
8de7d5d377 feat: css injection editor 2024-05-13 22:58:25 +08:00
Remember
952d7494ac Update 172.16.0.0/12 on Windows (#1013) 2024-05-13 18:28:41 +08:00
MystiPanda
9aeba20086 fix: use default bypass when empty 2024-05-13 18:27:19 +08:00
MystiPanda
9150c9c40e chore: Adapt Mac icon 2024-05-13 13:11:05 +08:00
MystiPanda
3e75897154 docs: Update README 2024-05-12 22:17:05 +08:00
MystiPanda
b0aa4402c2 chore: Change HomePage Logo 2024-05-12 21:17:21 +08:00
MystiPanda
41f80bcafd chore: Try a new icon 2024-05-12 19:31:06 +08:00
dongchengjie
c67359c49d chore: ru locale 2024-05-12 16:18:49 +08:00
RikudouPatrickstar
2f7c3cf21e chore: update notification message and zh translation (#1011) 2024-05-12 14:40:18 +08:00
MystiPanda
9731c8a750 perf: Disable Tun mode before shutting down 2024-05-12 11:06:44 +08:00
MystiPanda
8092e5c3a8 refactor: use axios tauri adapter 2024-05-11 21:54:56 +08:00
MystiPanda
f7ab8cc471 feat: Allow disable unused ports 2024-05-09 09:22:32 +08:00
MystiPanda
b593f62c4f chore: Update locale 2024-05-08 23:45:55 +08:00
MystiPanda
6905b7a410 perf: Limit drawing frame rate 2024-05-08 22:33:13 +08:00
MystiPanda
402f27b2a3 fix: bundle error 2024-05-08 14:52:38 +08:00
MystiPanda
c9c46d05d0 build: fix bundle error 2024-05-08 14:20:30 +08:00
MystiPanda
6591575d22 fix: depend error 2024-05-08 13:24:34 +08:00
MystiPanda
6064119779 Release 1.6.2 2024-05-08 13:14:00 +08:00
MystiPanda
00cd9b581d feat: support upgrade for release core 2024-05-08 12:48:27 +08:00
MystiPanda
a3d7b72485 docs: Update FAQ URL 2024-05-08 11:44:57 +08:00
MystiPanda
88c73be2f4 ci: update ci 2024-05-08 11:12:06 +08:00
MystiPanda
39a9181cdd refactor: remove prepend and append for provider 2024-05-08 00:22:14 +08:00
MystiPanda
0e5c6f56a0 feat: deep merge
#983
2024-05-08 00:13:32 +08:00
MystiPanda
5147a070a1 build: Add portable for fixed webview2 2024-05-06 19:13:08 +08:00
MystiPanda
b11be1838a ci: debug ci 2024-05-06 16:16:47 +08:00
MystiPanda
be99768a32 ci: fix ci script 2024-05-06 15:30:00 +08:00
MystiPanda
fe439a0cb6 ci: update release body 2024-05-06 15:15:07 +08:00
MystiPanda
87f49ec879 docs: update url 2024-05-06 15:05:56 +08:00
MystiPanda
20f2730125 build: try support fixed webview2 runtime 2024-05-06 14:18:12 +08:00
MystiPanda
bc5b34db6b ci: try resupport x86 2024-05-06 13:04:29 +08:00
dongchengjie
bcf9df3744 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
4d3674ee0a fix: enhance when change tun config 2024-05-04 16:12:10 +08:00
dongchengjie
28567e4629 chore: profile template typo 2024-05-04 14:24:11 +08:00
MystiPanda
1180a4fb0b fix: try to set env 2024-05-03 18:00:55 +08:00
dongchengjie
71928d2c9f fix: #974 2024-05-03 12:50:04 +08:00
MystiPanda
3853072a2e chore: update locale 2024-05-02 23:44:09 +08:00
dongchengjie
0cf630ef23 chore: add service mode locale 2024-05-02 22:16:05 +08:00
MystiPanda
202015fe34 feat: Support drag and drop local files 2024-05-02 20:41:43 +08:00
MystiPanda
ae43e5cae4 fix: change script execution path 2024-05-01 11:39:09 +08:00
MystiPanda
67b67bae6a Release 1.6.1 2024-04-30 23:43:51 +08:00
MystiPanda
dbb8fe15cf fix: MacOS tray click 2024-04-30 23:21:25 +08:00
lxk955
56efa10f64 feat: Support for using regular expressions on the log page #707 (#959) 2024-04-30 22:59:42 +08:00
MystiPanda
5ff776f90d fix: service install path 2024-04-30 20:20:39 +08:00
MystiPanda
a25b072bf6 build: Restore core version 2024-04-30 09:42:25 +08:00
dongchengjie
c95951c0e4 fixup: wrong version 2024-04-30 02:13:24 +08:00
dongchengjie
4c8193b801 chore: update pnpm-lock.yaml (#952)
* chore: update schema version

* Update pnpm-lock.yaml
2024-04-30 01:44:05 +08:00
dongchengjie
598a544ff8 chore: update schema version (#950) 2024-04-29 22:46:29 +08:00
MystiPanda
465ef3fa9a chore: Config issue template 2024-04-29 15:43:43 +08:00
MystiPanda
2f876d93e3 docs: Update FAQ URL 2024-04-28 20:04:03 +08:00
MystiPanda
84e8c44e4f build: cargo update 2024-04-28 19:59:46 +08:00
MystiPanda
855d794bdb fix: Open the link with browser 2024-04-28 18:46:49 +08:00
dongchengjie
a3333f8fe1 fixup: can't edit file (#943) 2024-04-28 17:00:33 +08:00
dongchengjie
2e64d62ca4 chore: disable WebView keyboard shortcuts (#942) 2024-04-28 16:19:13 +08:00
lxk955
26a3dbcbe1 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
fa2e86df29 fix: disable left click menu on macOS (#930) 2024-04-27 20:52:29 +08:00
LiuTianYu
b4f0ece78f 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
630b319a37 Release 1.6.0 2024-04-23 14:25:09 +08:00
MystiPanda
4a37e49798 feat: support ico format for tray icon (#911) 2024-04-22 20:43:15 +08:00
dongchengjie
cfbe98a39a fix: #907 (#908) 2024-04-22 01:40:25 +08:00
dongchengjie
91f097d514 chore: update locale (#904)
* chore: missing locale

* chore: External Controller locale
2024-04-21 14:10:34 +08:00
dongchengjie
c545521cd9 chore: Proxy Bypass placeholder (#901) 2024-04-20 19:27:17 +08:00
dongchengjie
11e0f49ada 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
19ce53128b fix: startup script blocking 2024-04-20 17:52:54 +08:00
MystiPanda
deccff623a fix: default value for tun 2024-04-20 17:42:36 +08:00
MystiPanda
b2a210ec0d build: add depends 2024-04-19 15:55:46 +08:00
MystiPanda
bdb5169a6f chore: update lock file 2024-04-19 15:38:32 +08:00
dongchengjie
0865b702a3 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
d13b8fd486 refactor: change default value of mtu 2024-04-18 10:27:09 +08:00
dongchengjie
494911805e chore: 修改Merge配置文件默认内容 (#889) 2024-04-17 22:56:54 +08:00
MystiPanda
cba3a2be24 chore: Update pnpm lockfile 2024-04-17 22:16:50 +08:00
dongchengjie
0686781359 feat: Clash配置、Merge配置提供JSON Schema语法支持、[连接]界面调整 (#887) 2024-04-17 21:19:37 +08:00
MystiPanda
e5b82dca4d fix: SymbolicLink
#750
2024-04-13 15:46:11 +08:00
MystiPanda
2144a42a22 fix: service install path 2024-04-13 15:12:41 +08:00
汐殇
07a989e004 fix: Allow core files in specific directories to be upgraded normally (#857) 2024-04-12 01:16:57 +08:00
dongchengjie
4f7e8116cb 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
c0f650d7dc fix: Update the home while updating profile 2024-04-04 15:07:55 +08:00
MystiPanda
d4a0136504 fix: Use System Browser 2024-04-04 14:56:10 +08:00
Damian Johnson
5f25e027c4 style: adjust confirm dialog & web ui settings dialog (#821) 2024-04-03 23:19:00 +08:00
Damian Johnson
9610dcce20 fix: service mode install script download not found (#822) 2024-04-03 23:18:10 +08:00
dongchengjie
3ee3e7c17b feat: support URL Schema 'profile-web-page-url' (#816) 2024-04-01 19:28:28 +08:00
HZ is not Chatty
98536250bd 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
e95808e6be feat: Try support service mode for MacOS 2024-03-31 23:16:47 +08:00
MystiPanda
9fc819a410 fix: script error 2024-03-31 16:37:33 +08:00
HZ is not Chatty
b0f1ce1fa0 feat: Service Mode for Linux (#804) 2024-03-31 16:16:23 +08:00
wonfen
503579a638 Sytle: fix mac logo padding 2024-03-30 16:31:17 +08:00
Damian Johnson
3ad216751a fix: icon not change when toggle window maxinized (#799) 2024-03-30 01:16:40 +08:00
cismous
ca8e3179bb 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
fd84e56c00 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
8b67fb7290 Release 1.5.11 2024-03-28 12:06:41 +08:00
MystiPanda
2d1fdb319d feat: Support Persian
#715
2024-03-21 20:51:33 +08:00
MystiPanda
c200e18434 fix: Duplicate icon display error
#719
2024-03-21 20:29:16 +08:00
MystiPanda
b3ffcd020f fix: The update button cannot be clicked
#716
2024-03-21 19:56:30 +08:00
MystiPanda
9258a3dcd4 fix: Check if the install directory is empty when uninstall 2024-03-21 15:24:12 +08:00
MystiPanda
57f5478731 chore: fix typo 2024-03-21 14:09:59 +08:00
MystiPanda
973d75ebdd Release 1.5.10 2024-03-21 13:47:39 +08:00
MystiPanda
33519b27c8 chore: Change a little 2024-03-21 12:55:22 +08:00
MystiPanda
7da7ff4a69 build: Update depends 2024-03-21 11:53:25 +08:00
MystiPanda
dbd2f697f9 fix: a little 2024-03-21 11:43:16 +08:00
MystiPanda
f435762b88 feat: Confirm before deletion
#703
2024-03-21 11:39:01 +08:00
MystiPanda
ae46332e42 feat: Support config redir port and tproxy port 2024-03-21 10:54:56 +08:00
MystiPanda
d003883de9 chore: limit port config
#699
2024-03-20 21:23:10 +08:00
MystiPanda
4e438a44f1 fix: Limit icon width
#697
2024-03-20 20:38:45 +08:00
MystiPanda
02e19b3d44 fix: service logs are not cleared
#695
2024-03-20 20:31:00 +08:00
MystiPanda
ccc19512e7 docs: Improve the issue template 2024-03-20 18:06:02 +08:00
MystiPanda
c34539e389 feat: Optimize Linux tray menu 2024-03-18 11:11:18 +08:00
MystiPanda
f8aeacb949 Revert "fix: remove activation policy" 2024-03-18 09:53:48 +08:00
MystiPanda
9940190679 Release 1.5.9 2024-03-17 11:25:25 +08:00
MystiPanda
e2498b3e91 fix: drag error
#643
2024-03-16 20:37:39 +08:00
MystiPanda
82246fd9c7 fix: drag error 2024-03-16 17:06:59 +08:00
MystiPanda
11538552eb fix: Try to fix touch drag
#456
2024-03-16 10:54:19 +08:00
MystiPanda
4ce28f54de build: Update to Tauri 1.6.1 2024-03-16 00:03:34 +08:00
MystiPanda
e887ed74a3 ci: update alpha script 2024-03-15 21:52:56 +08:00
MystiPanda
28c086e97c ci: remove 2024-03-15 21:42:55 +08:00
MystiPanda
11465e89a3 feat: Try to support more architecture 2024-03-15 21:14:05 +08:00
MystiPanda
b2197187c1 refactor: Try to migrate to boa_engine
#634
2024-03-15 19:58:22 +08:00
MystiPanda
2b26a10745 fix: Avoid empty user-agent 2024-03-15 18:27:24 +08:00
MystiPanda
daf726ebbf feat: Try to cache remote images
#603
2024-03-15 16:43:39 +08:00
screw-hand
88dd886687 fix: mac setting theme font-famil not working (#632) 2024-03-15 15:17:05 +08:00
MystiPanda
609da457f7 fix: remove activation policy
#592
2024-03-15 12:44:25 +08:00
xkww3n
363e28ff69 fix: Set REJECT-DROP policy the same text color as REJECT (#622) 2024-03-14 18:54:26 +08:00
MystiPanda
bdd6bf9020 Release 1.5.8 2024-03-13 14:17:06 +08:00
wonfen
5c3dab3466 Sytle: A little tweak 2024-03-13 14:09:08 +08:00
MystiPanda
9b2c8fa25d feat: Add border-radius for window on linux 2024-03-13 13:58:29 +08:00
Amnesiash
df2f102d9e update profile ui (#594) 2024-03-13 10:53:39 +08:00
MystiPanda
95ebb0e6d2 fix: Try to fix #577 again 2024-03-11 22:52:29 +08:00
MystiPanda
e5d03652a9 fix: script 2024-03-11 22:10:20 +08:00
MystiPanda
56b53e2dd8 fix: typo 2024-03-11 22:05:56 +08:00
MystiPanda
cf0b7b213f fix: Try to fix #577 2024-03-11 22:03:45 +08:00
MystiPanda
d214c8e01b feat: Allow open devtools 2024-03-11 20:19:21 +08:00
MystiPanda
fba0f362a5 docs: Update Readme 2024-03-11 18:38:58 +08:00
MystiPanda
ec05d0857c Release 1.5.7 2024-03-11 17:09:01 +08:00
MystiPanda
54b744b7de fix: display error 2024-03-11 16:18:39 +08:00
MystiPanda
1a76780fff chore: Change IP 2024-03-11 16:05:09 +08:00
MystiPanda
58f5c44533 fix: a little 2024-03-11 15:02:41 +08:00
MystiPanda
d085da4dbf fix: Change DNS for MacOS Tun Mode
#568
2024-03-11 14:55:00 +08:00
MystiPanda
c4a5c356f7 fix: default value 2024-03-11 13:17:45 +08:00
MystiPanda
18fdc5c6a2 fix: Try to fix touch drag
#456
2024-03-11 12:36:20 +08:00
MystiPanda
35dabaab9c feat: Allow to control whether auto check update 2024-03-11 12:17:46 +08:00
MystiPanda
9bf31b10bb style: fix a little 2024-03-10 23:56:04 +08:00
MystiPanda
7186575cb1 style: fix styles 2024-03-10 23:47:12 +08:00
MystiPanda
2ecae40130 fix styles 2024-03-10 22:13:25 +08:00
MystiPanda
778ed62a90 chore: proxy group header height 2024-03-10 21:56:49 +08:00
MystiPanda
5e863a87dc chore: adjust proxy group font style 2024-03-10 21:52:40 +08:00
MystiPanda
7ce8597c25 chore: Adjust Profile Style 2024-03-10 21:37:52 +08:00
MystiPanda
1992237ce5 chore: Add Default value 2024-03-10 21:15:22 +08:00
MystiPanda
8f247b0f73 style: Adjust icon color 2024-03-10 20:48:13 +08:00
Amnesiash
e2159d80af style: Adjust colorful icon (#558)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-10 20:37:00 +08:00
MystiPanda
057023531e chore: i18n 2024-03-10 20:33:27 +08:00
MystiPanda
dfed65bf9f chore: Update Icon 2024-03-10 20:14:51 +08:00
MystiPanda
739161849a feat: Add option to control menu icon 2024-03-10 19:54:47 +08:00
MystiPanda
c65b280020 chore: Adjust styles 2024-03-10 13:24:38 +08:00
MystiPanda
d3bcf25ef0 Revert "chore: Adjust secondary text style (#545)"
This reverts commit f6bd3340e74a8ac4f30d3c8c2997a9935db6803a.
2024-03-10 13:08:09 +08:00
Amnesiash
f6bd3340e7 chore: Adjust secondary text style (#545) 2024-03-10 12:54:34 +08:00
MystiPanda
6a7c09bfe3 style: Adjust delay fontSize
#544
2024-03-10 12:51:53 +08:00
MystiPanda
8b9f294a5d docs: update preview 2024-03-10 12:32:08 +08:00
MystiPanda
4a282d9629 chore: Change Default Font 2024-03-10 12:23:11 +08:00
MystiPanda
909b88864f chore: delete debug output 2024-03-10 11:47:55 +08:00
MystiPanda
1346a7992c Release 1.5.6 2024-03-10 11:43:37 +08:00
MystiPanda
bba607e987 feat: Add default webui
#530
2024-03-10 11:41:22 +08:00
MystiPanda
f9cc490c35 Adjust styles 2024-03-10 11:21:17 +08:00
MystiPanda
a5aec2d9fa Adjust styles 2024-03-10 11:12:54 +08:00
wonfen
c0df368dc6 Sytle: UI improvement & Update Readme 2024-03-10 07:00:24 +08:00
MystiPanda
aa77433523 fix: fix a little 2024-03-10 00:41:18 +08:00
black23eep
44ad99f693 Update layout-traffic.tsx (#528) 2024-03-10 00:35:26 +08:00
MystiPanda
b7f7a82ea9 chore: update 2024-03-10 00:34:22 +08:00
MystiPanda
c69978c9fd fix: fontSize and some styles 2024-03-10 00:22:22 +08:00
Amnesiash
6174aa6ee1 fix settings.svg (#526) 2024-03-10 00:04:06 +08:00
MystiPanda
74b8d2e908 chore: center 2024-03-09 23:38:03 +08:00
Charles
63a515944f tweak(ui): menu icon use svg component (#524) 2024-03-09 23:13:08 +08:00
MystiPanda
5b5db7b860 fix: img path error 2024-03-09 22:43:53 +08:00
MystiPanda
b3bbacf2ef Release 1.5.5 2024-03-09 22:02:36 +08:00
Amnesiash
3a0429d049 refactor: Upgrade to the new UI (#521)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-09 21:37:21 +08:00
wonfen
ab539081fa UI: change paste icon, delete default profile name 2024-03-09 02:34:33 +08:00
Pylogmon
f0d88d4e73 feat: Merge Providers (#508) 2024-03-08 11:37:52 +08:00
MystiPanda
772cbd6ffd feat: Add animation for provider update 2024-03-02 13:52:48 +08:00
MystiPanda
17b9dbe9d7 chore: fix style 2024-02-28 18:25:50 +08:00
MystiPanda
75e5d42d8b feat: Try to support touch drag 2024-02-27 23:41:52 +08:00
MystiPanda
031c15fd7d chore: Remove unnecessary hotkey 2024-02-27 11:18:52 +08:00
MystiPanda
de1924cefc fix: Image display failed on Linux 2024-02-26 13:31:51 +08:00
Cyenoch
66db0a4751 Feat: Provide a switch for allowing invalid certificates (#450) 2024-02-25 16:07:06 +08:00
MystiPanda
c309410965 chore: change style 2024-02-24 15:39:29 +08:00
MystiPanda
7f461b99e2 Release 1.5.4 2024-02-24 14:00:01 +08:00
MystiPanda
7bfe0eeae9 feat: Support Open/Close Dashboard Hotkey
#439
2024-02-24 13:39:30 +08:00
MystiPanda
8619bd5be3 feat: Show current proxy for group node
#444
2024-02-24 13:09:53 +08:00
MystiPanda
56011d37d4 feat: allow disable group icon 2024-02-24 12:38:17 +08:00
MystiPanda
c2852c8a82 refactor: Optimize implementation of Custom tray icon 2024-02-24 11:25:22 +08:00
MystiPanda
6f546a424e feat: Support Custom Tray Icon 2024-02-24 00:52:21 +08:00
MystiPanda
447f7530af Release 1.5.3 2024-02-22 00:23:56 +08:00
MystiPanda
6136f1206b feat: add reset button 2024-02-22 00:19:45 +08:00
MystiPanda
36a3c5b501 fix: Do not set autolaunch at init
#423 #424
2024-02-21 23:50:50 +08:00
MystiPanda
dcd6c1f522 chore: rm -m arg 2024-02-21 23:38:01 +08:00
MystiPanda
2b074bcdcb chore: change default value of dns hijack 2024-02-21 16:40:47 +08:00
MystiPanda
b20ec7f0eb chore: change default value of strict route 2024-02-21 11:32:51 +08:00
MystiPanda
58cf69a2fe chore: fix placeholder 2024-02-21 11:13:28 +08:00
MystiPanda
096c148228 chore: Add Translation 2024-02-21 11:06:32 +08:00
MystiPanda
a68005d4ab feat: Disable system stack when service mode is turned off 2024-02-21 10:52:03 +08:00
MystiPanda
bf3a281987 fix: Config data display error
#417
2024-02-21 10:02:28 +08:00
MystiPanda
2965a6827d Release 1.5.2 2024-02-21 00:04:11 +08:00
MystiPanda
9f43a73c36 chore: Auto Update config.yml 2024-02-20 23:54:02 +08:00
MystiPanda
7551b45da2 feat: Support Tun Config (#416) 2024-02-20 23:27:03 +08:00
MystiPanda
bca3685eda chore: Update patch file 2024-02-20 18:39:36 +08:00
MystiPanda
6d20175800 fix: Allow program run with administrator to start at startup 2024-02-20 18:35:39 +08:00
MystiPanda
a62dd4c020 chore: add tooltip for tun mode 2024-02-19 18:10:10 +08:00
MystiPanda
d1d9620a61 feat: Support custom delay timeout (#397) 2024-02-18 11:11:22 +08:00
MystiPanda
5106d77c77 build: Try restart windows service after install (#395) 2024-02-18 10:19:54 +08:00
MystiPanda
69cf237d7a fix: Merge profile unexpect behavior 2024-02-17 15:58:52 +08:00
MystiPanda
7d9d1c82b6 ci: Update Release Note 2024-02-15 20:05:00 +08:00
MystiPanda
228ff5edf4 ci: change tag 2024-02-15 19:45:37 +08:00
MystiPanda
2baac618a8 chore: update ci script 2024-02-15 19:14:35 +08:00
MystiPanda
e8f499a938 chore: Change CI Name 2024-02-15 18:45:13 +08:00
MystiPanda
96ca20d0b4 ci: Support alpha package 2024-02-15 18:43:20 +08:00
MystiPanda
54acdc86e7 chore: remove 32bit package 2024-02-15 18:22:15 +08:00
MystiPanda
0815e02895 chore: default show proxy detials 2024-02-15 18:17:21 +08:00
wonfen
30cbb72b57 doc: remove 32bit package 2024-02-12 02:25:18 +08:00
MystiPanda
0b91397709 fix: build error 2024-02-11 20:51:00 +08:00
MystiPanda
89934c17ca Release v1.5.1 2024-02-11 20:32:25 +08:00
MystiPanda
e4a38e62eb style: clear script 2024-02-11 20:03:14 +08:00
MystiPanda
5e1e09d7bf fix: Custom GLOBAL group display error 2024-02-11 18:27:27 +08:00
MystiPanda
51ce3a1e42 feat: Show proxies count for provider 2024-02-10 13:13:27 +08:00
MystiPanda
54b46dfad9 feat: Save window maximize state 2024-02-10 12:51:30 +08:00
MystiPanda
787917ac66 style: change config name
#350
2024-02-08 10:23:54 +08:00
MystiPanda
619b49bdc4 fix: Script profile invalid
#347
2024-02-06 08:55:44 +08:00
MystiPanda
4b3a73d440 Release 1.5.0 2024-02-06 00:41:12 +08:00
MystiPanda
54f9c59d6e refactor: Remove clash field filter 2024-02-05 17:28:36 +08:00
wonfen
1d123996f6 chore: enable clash field TLS fingerprint 2024-02-04 13:07:25 +08:00
MystiPanda
c4768f6138 fix: Try to fix traffic parse error
#337
2024-02-04 10:24:37 +08:00
wonfen
5f3551ff34 chore: update changelog 2024-02-03 16:14:26 +08:00
MystiPanda
d3985c2e3b fix: Copy Env Type Select Error 2024-02-02 17:57:56 +08:00
MystiPanda
3b1843f3a3 chore: typo 2024-02-02 17:47:28 +08:00
MystiPanda
2c36796362 chore: Update Changelog 2024-02-02 17:34:15 +08:00
MystiPanda
655ccba89b fix: prevent_exit 2024-02-02 17:26:31 +08:00
MystiPanda
150d72f0f8 Release 1.4.11 2024-02-02 16:56:39 +08:00
MystiPanda
6a316b34a2 fix: exit_app event 2024-02-02 16:32:19 +08:00
MystiPanda
8e6b600609 chore: fix styles 2024-02-02 16:15:23 +08:00
MystiPanda
5630a4dd67 chore: Remove prevent_close 2024-02-02 15:57:03 +08:00
MystiPanda
38ee8aedc1 fix: Fix Nisi Error 2024-02-02 15:53:28 +08:00
ycjcl868
d594615532 chore: unused clash core (#284) 2024-01-23 11:22:15 +08:00
MystiPanda
2739fa60be chore: fix style 2024-01-21 13:49:07 +08:00
MystiPanda
327301782d revert: some style 2024-01-21 13:49:07 +08:00
wonfen
8781c5db8d Sytle: UI improvement 2024-01-20 16:05:01 +08:00
Lai Zn
31c34ea158 fix: Solve the confliction issues on auto updater failure (#273) 2024-01-20 13:03:48 +08:00
MystiPanda
ef9bbaca19 revert: Support both registry and api for windows sysproxy 2024-01-20 12:16:46 +08:00
MystiPanda
02d89072bc build: Update lock file 2024-01-18 22:34:14 +08:00
MystiPanda
4e8bd640a7 Release v1.4.10 2024-01-18 22:26:23 +08:00
MystiPanda
8c71a00600 feat: Add exit button on setting page 2024-01-18 22:19:14 +08:00
MystiPanda
3d36c70d53 build: Update Depends 2024-01-18 16:26:38 +08:00
MystiPanda
c72479c4d6 feat: Support Custom Start Page 2024-01-18 15:37:02 +08:00
Lai Zn
4bb88d8e44 feat: Use url path name as fallback subscription name (#255) 2024-01-18 14:36:37 +08:00
MystiPanda
0ee0958539 feat: Show SubInfo for Proxy Provider
#211
2024-01-18 14:26:57 +08:00
Lai Zn
b6dd6f3a94 fix: make port change set to system proxy immediately (#256) 2024-01-18 09:37:46 +08:00
MystiPanda
d11c322e1f fix: use nanoid to compatible with old devices 2024-01-18 01:16:39 +08:00
MystiPanda
cd92b34ef1 feat: Optimizing Provider Support 2024-01-18 01:02:30 +08:00
Kiri
b6481cfcda feat: Add support for loong64 (#246) 2024-01-17 22:30:51 +08:00
Lai Zn
db7eb92638 docs: Add guidelines for windows development (#250) 2024-01-17 19:02:39 +08:00
MystiPanda
f2d0477550 fix: Do not use proxy when test on tun mode 2024-01-17 18:08:09 +08:00
MystiPanda
776e207f09 Release v1.4.9 2024-01-17 15:43:15 +08:00
MystiPanda
733e8a0043 feat: Support Startup Script 2024-01-17 15:06:16 +08:00
MystiPanda
4fa19006ad feat: Support proxy group icon 2024-01-17 13:32:56 +08:00
MystiPanda
73a597e3e5 fix: Fix connection table sort error
#108
2024-01-17 12:54:54 +08:00
MystiPanda
d776f1765d chore: Update I18n 2024-01-17 11:30:19 +08:00
MystiPanda
741b6f6f9a fix: csp error 2024-01-17 11:08:14 +08:00
MystiPanda
b6f4695bcd feat: Add Test Page 2024-01-17 11:02:17 +08:00
MystiPanda
b7d3b807d2 fix: fix column width 2024-01-16 19:02:38 +08:00
MystiPanda
0cc386bc28 Release v1.4.8 2024-01-16 15:36:04 +08:00
MystiPanda
cb155707cd feat: Support both registry and api for windows sysproxy 2024-01-16 15:11:53 +08:00
MystiPanda
9b6b250cbd fix: Can not use specify update time when create profile 2024-01-16 10:29:04 +08:00
MystiPanda
5b7e29b8ad refactor: rm theme blur 2024-01-15 17:13:55 +08:00
MystiPanda
b6d748b414 Revert Use Tauri Http Api 2024-01-15 10:18:04 +08:00
MystiPanda
8fc4b338c2 Revert Use Tauri Websocket 2024-01-15 10:17:00 +08:00
MystiPanda
6d3ea19ac5 fix: Fix connections sort issue and add total traffic info 2024-01-15 00:46:19 +08:00
MystiPanda
d01ef48bf0 refactor: Use Tauri Http API 2024-01-14 19:35:03 +08:00
MystiPanda
71103bb7b9 refactor: Use Tauri WebSocket 2024-01-14 17:30:18 +08:00
MystiPanda
1c9bc00acc fix patch error 2024-01-11 14:47:25 +08:00
MystiPanda
bed128e8cf Release v1.4.7 2024-01-11 13:19:05 +08:00
MystiPanda
b5c3f18f24 feat: Disable updater for portable 2024-01-11 12:44:30 +08:00
MystiPanda
cbccdf5d93 feat: Support hide group
#214
2024-01-11 12:34:05 +08:00
Morris Li
a46f3a31e1 Fix expired Tauri domain (#222) 2024-01-11 10:17:56 +08:00
MystiPanda
a808f7b04e build: Use old sysproxy 2024-01-10 19:24:14 +08:00
MystiPanda
3a883b9e41 refactor: cargo clippy 2024-01-10 17:36:35 +08:00
MystiPanda
17d8691300 refactor: Optimizing the implementation of Linux URL Scheme registration 2024-01-10 16:34:35 +08:00
MystiPanda
523ce1dbdd fix: resolve scheme error 2024-01-10 15:37:40 +08:00
MystiPanda
4a3a9bf62c fix: linux build error 2024-01-10 15:22:08 +08:00
MystiPanda
3b30177959 feat: Support URL Scheme for MacOS 2024-01-10 14:04:06 +08:00
MystiPanda
7e66f89260 feat: Support URL Scheme for Linux 2024-01-10 13:03:34 +08:00
MystiPanda
1a93ba634f build: fix macos build script 2024-01-09 22:49:11 +08:00
MystiPanda
d93f823fc6 fix patch error 2024-01-09 22:25:25 +08:00
MystiPanda
f2198bf938 Release v1.4.6 2024-01-09 22:17:29 +08:00
MystiPanda
965f10698b feat: Support URL Scheme for Windows
#165
2024-01-09 21:57:06 +08:00
MystiPanda
b71367cd2a feat: Optimize control button style 2024-01-09 16:04:56 +08:00
MystiPanda
abe18ac825 feat: Add pin button 2024-01-09 15:13:59 +08:00
MystiPanda
bb193d3768 fix: Fix some compile error 2024-01-09 14:52:43 +08:00
wonfen
7a7f5cd4a8 Style: UI improvement & 1.4.6 ready 2024-01-09 13:57:53 +08:00
wonfen
ac9f49f8c9 chore: UI adjustment 2024-01-08 19:32:21 +08:00
MystiPanda
8f53859e00 chore: Use Latest and compatible core 2023-12-23 12:09:19 +08:00
MystiPanda
bfb7ff88d9 fix: Cargo clippy 2023-12-21 16:49:21 +08:00
MystiPanda
c36425fd3a fix: Get filename error
#165
2023-12-19 19:52:13 +08:00
MystiPanda
981f9d0b01 fix: portable flag 2023-12-15 21:39:34 +08:00
MystiPanda
fa89fe3e87 chore: update service url 2023-12-15 20:38:17 +08:00
MystiPanda
d132357c20 fix: user-agent version error 2023-12-15 15:18:01 +08:00
MystiPanda
a719237556 fix: Subinfo parse error 2023-12-15 11:35:10 +08:00
MystiPanda
8955ca5216 Release 1.4.5 2023-12-14 17:40:48 +08:00
MystiPanda
f665762cc8 fix: Window control button icon issue
#136
2023-12-14 14:59:55 +08:00
MystiPanda
d16fc2b68b chore: Remove unsafe function 2023-12-14 13:56:51 +08:00
MystiPanda
e1df32c32d fix: icon size 2023-12-14 13:20:01 +08:00
MystiPanda
943c6f77dc feat: Update MacOS tray icon 2023-12-14 13:11:46 +08:00
MystiPanda
4964382966 chore: Optimize service path 2023-12-14 13:03:52 +08:00
MystiPanda
021c6fdbe2 chore: Optimize upgrade process 2023-12-14 11:24:26 +08:00
MystiPanda
711f9805c9 chore: default enable clash field filter 2023-12-14 10:49:58 +08:00
MystiPanda
0aaae3afd6 chore: Delete unnecessary drag area 2023-12-13 11:09:19 +08:00
MystiPanda
eadd1042fb chore: Change service path 2023-12-13 10:59:07 +08:00
MystiPanda
16fa2c9f5e fix: Save wrong window size 2023-12-12 16:42:19 +08:00
MystiPanda
d4ab1df870 Revert "fix: Change PID file path"
This reverts commit c5855119d8c97d2eee7c66c9ec115c6d3999e36a.
2023-12-12 15:59:00 +08:00
MystiPanda
4a5aa1bcc1 fix: Can't switch env type 2023-12-11 10:51:59 +08:00
MystiPanda
01a9cda99a build: fix update issue 2023-12-10 22:24:46 +08:00
MystiPanda
dcdf606ff6 build: fix patch 2023-12-10 21:40:57 +08:00
MystiPanda
ea021de5eb v1.4.4 2023-12-10 21:36:22 +08:00
MystiPanda
5cc3526f8f feat: Support windows aarch64 (#112) 2023-12-10 20:45:27 +08:00
MystiPanda
3060fc2af4 feat: Patch for windows aarch64 2023-12-10 19:50:01 +08:00
MystiPanda
e4395dfeb4 build: Update Depends 2023-12-10 17:14:48 +08:00
MystiPanda
f65dadf1d7 chore: Hide script mode for alpha core 2023-12-10 16:31:55 +08:00
MystiPanda
f048762fd9 Support upgrade alpha core 2023-12-10 15:57:10 +08:00
MystiPanda
bb985f826e feat: Support update geodata
#37
2023-12-10 15:22:04 +08:00
zclkkk
bb239cba2a chore: Allow user to choose where to install (#110) 2023-12-10 09:50:43 +08:00
MystiPanda
e7e66e580a chore: Remove script mode 2023-12-09 17:04:03 +08:00
MystiPanda
689d689d3b docs: Update ISSUE_TEMPLATE 2023-12-09 12:00:01 +08:00
MystiPanda
125c3d3e0d feat: Support different tray icon for macos 2023-12-09 11:22:02 +08:00
MystiPanda
ffaa06560e feat: Support different tray icon for linux 2023-12-09 11:03:19 +08:00
Pylogmon
f9b716201f feat: Optimize copy environment variable logic (#106) 2023-12-08 22:16:42 +08:00
MystiPanda
8b14a5f0d8 perf: Improves window creation speed 2023-12-08 13:10:35 +08:00
MystiPanda
c5855119d8 fix: Change PID file path
#99
2023-12-08 11:59:40 +08:00
MystiPanda
a036597f5f docs: Update README 2023-12-07 16:49:59 +08:00
MystiPanda
b8688e2e66 feat: Add AppImage for x86 linux 2023-12-07 16:38:39 +08:00
MystiPanda
d8b2e08717 ci: fix build script 2023-12-07 16:17:34 +08:00
MystiPanda
7da78d3312 chore: Fix build error 2023-12-07 16:02:29 +08:00
MystiPanda
2292b107dc ci: Fix Linux CI Script 2023-12-07 15:57:53 +08:00
MystiPanda
1fcc74c658 ci: Fix Linux Build Script 2023-12-07 15:34:49 +08:00
MystiPanda
73be027951 chore: change default port to 7897 2023-12-07 14:52:14 +08:00
MystiPanda
132d91c2ab refactor: fix depends 2023-12-07 14:44:44 +08:00
MystiPanda
f08ce82c3f refactor: Remove unnecessary changes 2023-12-07 13:43:53 +08:00
MystiPanda
8de2712652 Revert "chore: change default port to 7897"
This reverts commit 55835785c095094c841db75934ca1b685c625c1b.
2023-12-07 13:32:49 +08:00
wonfen
55835785c0 chore: change default port to 7897 2023-12-06 03:37:16 +08:00
MystiPanda
cb711b7758 docs: Update README 2023-12-05 17:02:54 +08:00
MystiPanda
19c75293bf docs: UPDATELOG 2023-12-05 16:47:42 +08:00
MystiPanda
33219f00d5 Release 1.4.3 2023-12-05 16:28:50 +08:00
MystiPanda
4aaedbb0e6 chore: fix appid 2023-12-05 15:41:13 +08:00
MystiPanda
ee1f14d4eb refactor: Change app_home to standard locations of app_data 2023-12-05 15:39:44 +08:00
MystiPanda
feac8085c9 fix: Stop core before install update 2023-12-05 15:01:15 +08:00
MystiPanda
0a5ec11b1b docs: Update README 2023-12-05 13:33:23 +08:00
MystiPanda
03937174e5 refactor: Remove page animation 2023-12-05 13:30:55 +08:00
MystiPanda
e00529c05f refactor: Change Tun Icon Color 2023-12-05 13:23:54 +08:00
MystiPanda
5dbf8e1d2b build: Dependency downgrade 2023-12-05 12:55:20 +08:00
Pylogmon
bebf672186 refactor: Change Portable Config Path (#66) 2023-12-05 12:52:26 +08:00
Pylogmon
6d4a8f2a5a refactor: Hide Clash Field Option when Disable Filter (#63) 2023-12-05 08:34:57 +08:00
MystiPanda
14db0ae663 Update README.md 2023-12-04 14:51:46 +08:00
MystiPanda
88d1c1d140 ci: fix updater 2023-12-04 13:51:46 +08:00
MystiPanda
9106055be1 chore: Use Nsis 2023-12-04 13:45:28 +08:00
Pylogmon
2cf52f15ab fix: Open File (#56) 2023-12-04 12:15:12 +08:00
Pylogmon
b7e9d61c72 fix: Get Profile Filename (#54) 2023-12-04 12:15:01 +08:00
MystiPanda
0bc22db296 chore: change linux meta core to compatible 2023-12-04 11:38:54 +08:00
wonfen
6a943acc70 chore: change windows meta core to compatible 2023-12-04 06:00:31 +08:00
WhizPanda
7c97416e7d fix: alpha core can't display memory 2023-12-03 18:55:38 +08:00
WhizPanda
dd75504a66 style: fix icon size 2023-12-03 18:28:41 +08:00
WhizPanda
04c4ab2289 docs: Update README.md 2023-12-03 16:42:49 +08:00
Pylogmon
310a2e2511 style: improve drag icon style (#51) 2023-12-03 16:27:13 +08:00
WhizPanda
f6314431f0 ci: Fix Portable Script 2023-12-03 15:36:39 +08:00
wonfen
89100d0ca0 docs: update readme 2023-12-03 15:25:33 +08:00
WhizPanda
2b646636c1 feat: Support Both Stable and Alpha Version (#47) 2023-12-03 14:26:03 +08:00
wonfen
b10c1d5006 release: 1.4.2, tweak UI, fix emoji on mac 2023-12-03 14:01:53 +08:00
WhizPanda
225b829c1a chore: Adjust style 2023-12-02 23:20:04 +08:00
WhizPanda
5f4c7076ab chore: Replace Repo Name 2023-12-02 23:04:04 +08:00
WhizPanda
61c9b304d7 build: Update Depends 2023-12-02 22:36:04 +08:00
WhizPanda
789d7000cf style: Improve Style 2023-12-02 16:23:53 +08:00
WhizPanda
d23ef2bd59 feat: Support New Clash Field
#46
2023-12-02 15:18:54 +08:00
WhizPanda
8a77f832a3 build: Update Cargo Depends 2023-12-01 15:52:49 +08:00
WhizPanda
5a6d318cfb build: Update Depends 2023-12-01 14:49:32 +08:00
WhizPanda
e9f14de05d feat: support random mixed port 2023-12-01 12:56:18 +08:00
WhizPanda
2d8da45bda fix: Fix Updater Script 2023-12-01 11:19:49 +08:00
WhizPanda
83de33f5b8 Remove unnecessary conditions 2023-12-01 11:11:20 +08:00
Pylogmon
e63844f786 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
0a805c16fd feat: Use Latest Meta Core mihomo (#41) 2023-12-01 02:44:18 +08:00
Pylogmon
653c7d4430 feat: Support cross-compiling to aarch64 (#40)
#19
2023-11-30 22:46:09 +08:00
Pylogmon
306c3bea21 feat: Support Disable Tray Click Event (#38)
#21
2023-11-30 22:45:02 +08:00
Pylogmon
389ce60bc9 feat: Set different tray icon on tun mode (#33)
好看!
2023-11-30 01:29:11 +08:00
Pylogmon
2d453a1a6c feat: Add Download Progress for Updater (#34) 2023-11-30 01:28:46 +08:00
Pylogmon
0775560ad2 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
2680c1e8b3 Merge pull request #27 from Pylogmon/emoji-font
feat: Embed Emoji fonts
2023-11-28 10:41:35 +08:00
Pylogmon
50ba2e3ad4 feat: Embed Emoji fonts 2023-11-28 10:36:32 +08:00
wonfen
c16875d0de fix: Adjust font order 2023-11-28 10:03:01 +08:00
wonfen
a4205cd0c2 chore: update preview gif 2023-11-28 08:49:22 +08:00
wonfen
2fb3e373c6 Merge branch 'main' of github.com:wonfen/clash-verge-rev 2023-11-28 07:53:11 +08:00
wonfen
ac1fa7209c update clashmeta core, Imporve UI, merge PR, reset icons, fix CI 2023-11-28 07:49:44 +08:00
wonfen
fd820d6af8 Merge pull request #24 from Pylogmon/tray
fix: Tray Icon Tooltip is Empty
2023-11-28 01:46:41 +08:00
wonfen
b88486601b Merge pull request #21 from Pylogmon/tray-event
feat: Config Tray Click Event
2023-11-28 01:46:16 +08:00
Pylogmon
92e712a508 chore: fix style 2023-11-27 20:10:31 +08:00
Pylogmon
64a9079ce4 chore: Add Translation 2023-11-27 20:04:47 +08:00
Pylogmon
f18d0ab923 fix: Tray Icon Tooltip is Empty 2023-11-27 19:55:42 +08:00
Pylogmon
def49e6d20 chore: Remove Debug Info 2023-11-26 18:49:48 +08:00
Pylogmon
f142db3d49 feat: Config Tray Click Event 2023-11-26 18:35:21 +08:00
wonfen
e7b04a89e2 fix: portable CI again 2023-11-23 15:01:30 +08:00
wonfen
1cb59f46e3 chore: change preview gif and fix portable CI 2023-11-23 14:34:11 +08:00
wonfen
a604746e0b fix: CI portable 2023-11-23 13:42:16 +08:00
wonfen
aba706a293 Merge pull request #3 from Kuingsmile/main
fix: Downgrade runas to fix service install bug
2023-11-23 11:11:06 +08:00
wonfen
c1b9113347 chore: update clash.meta core to 2023.11.23, change win icons. 2023-11-23 11:10:32 +08:00
Kuingsmile
5e7db2807d fix: Downgrade runas to fix service install bug 2023-11-22 17:17:12 -08:00
wonfen
203f830a30 Merge pull request #2 from Kuingsmile/main
feat: Update DialogContent width in EditorViewer
2023-11-23 07:46:12 +08:00
Kuingsmile
3dbe9193e2 feat: Update DialogContent width in EditorViewer
component
2023-11-22 06:49:47 -08:00
wonfen
717cf29595 Merge pull request #1 from Kuingsmile/main
feat: add UWP loopback tools
2023-11-22 16:23:38 +08:00
Kuingsmile
72300fec5e feat: add UWP loopback tools 2023-11-22 00:15:41 -08:00
wonfen
ec50b1d67a chore: UI adjustment, add translation, fix CI 2023-11-22 14:52:14 +08:00
wonfen
408a4420c9 chore: update readme & fix updater issue 2023-11-22 07:31:38 +08:00
wonfen
568218204a chore: remove old updater CI 2023-11-22 07:06:08 +08:00
wonfen
bd39e98c66 Merge remote-tracking branch 'nyanpasu/main' 2023-11-22 06:48:30 +08:00
wonfen
01be65a624 chore: delete clash core, update CI, change profile name, change URL test link 2023-11-22 02:56:47 +08:00
keiko233
a59d8a6a17 chore: fix typos 2023-11-16 11:31:06 +08:00
keiko233
70b71aa4f9 chore: add updater workflow 2023-11-16 10:59:27 +08:00
keiko233
aaf7991139 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
265d579fd9 chore: no need for second build 2023-11-16 10:41:28 +08:00
keiko233
f6dd91e47c chore: fix missing assets type 2023-11-16 10:37:26 +08:00
keiko233
cdae552ab0 chore: remove un supported platform 2023-11-16 10:30:25 +08:00
keiko233
730e887454 chore: fix typos 2023-11-16 10:30:01 +08:00
keiko233
5a3852d82c docs: add preview gif 2023-11-15 15:17:51 +08:00
keiko233
5b35580d2d chore: clean up workflows 2023-11-15 14:37:10 +08:00
keiko233
4b0705fe36 Bump Version 1.4.0 2023-11-15 14:06:29 +08:00
keiko233
d219b4d12a chore: add release build 2023-11-15 13:52:56 +08:00
keiko233
6e38331328 fix: rust lint 2023-11-15 13:49:06 +08:00
keiko233
c4bc9aea22 chore: test: use pnpm 2023-11-13 14:49:28 +08:00
keiko233
6c692d9308 feat: minor tweaks 2023-11-12 00:10:23 +08:00
keiko233
7ce8bd8988 feat: Nyanpasu Misc 2023-11-12 00:09:32 +08:00
keiko233
b4ead4076a chore: switch updater endpoints 2023-11-12 00:08:23 +08:00
keiko233
052893bbf8 fix: valid with unified-delay & tcp-concurrent 2023-11-11 22:34:30 +08:00
keiko233
a319fe8632 chore: delete current release assets 2023-11-11 22:27:28 +08:00
keiko233
8a84e68c13 feat: add baseContentIn animation 2023-11-11 19:34:03 +08:00
keiko233
0ca7defe83 feat: add route transition 2023-11-11 18:32:11 +08:00
keiko233
b704706ee9 feat: Material You! 2023-11-11 17:12:57 +08:00
keiko233
23fb634847 fix: touchpad scrolling causes blank area to appear 2023-11-11 15:03:41 +08:00
keiko233
0bd37eb8f9 chore: drop upload-artifact & use prerelease 2023-11-11 15:00:54 +08:00
keiko233
b86294e9cc chore: fix: typos
* ↑ baka
2023-11-10 11:50:01 +08:00
keiko233
f11f968f99 fix: typos 2023-11-10 11:35:38 +08:00
keiko233
ad81642954 fix: download clash core from backup repo 2023-11-10 11:26:05 +08:00
keiko233
ea42103ae7 chore: add dev workflow 2023-11-10 10:38:41 +08:00
keiko233
82435e37be feat: default disable ipv6 2023-11-10 09:10:15 +08:00
keiko233
b61d29b22a feat: default enable unified-delay & tcp-concurrent with use meta core 2023-11-10 09:08:17 +08:00
keiko233
04392a6a4c refactor: copy_clash_env 2023-11-09 10:52:52 +08:00
keiko233
6e9798d596 feat: support copy CMD & PowerShell proxy env 2023-11-09 09:58:17 +08:00
keiko233
bef553c9ae fix: use meta Country.mmdb 2023-11-09 09:35:41 +08:00
keiko233
7a76efa7a0 feat: default use meta core
* :)
2023-11-09 09:34:03 +08:00
keiko233
c5f64374ed 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
b767caa704 chore: fix check 2023-11-03 16:00:34 +08:00
GyDi
63974f97ab chore: fix check script 2023-11-03 15:52:36 +08:00
MZhao
37764a7caa feat: new windows tray icons (#899) 2023-11-03 15:01:46 +08:00
Aromia
fb586f0043 chore: update download links in README.md (#896) 2023-11-03 11:21:20 +08:00
Goooler
9990f4d8cf 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
80f73cc5d0 chore: add fedora linux install description (#874) 2023-11-02 20:13:24 +08:00
GyDi
8775f67416 feat: support auto clean log files 2023-11-02 20:12:46 +08:00
GyDi
2ce302f6f8 fix: latency url empty 2023-11-01 23:22:30 +08:00
GyDi
69b9944b8e feat: increase the concurrency of latency test 2023-11-01 20:52:38 +08:00
GyDi
37f35e8b2d fix: change default port 2023-10-31 14:56:19 +08:00
GyDi
8ad9531fa6 fix: csp 2023-10-31 12:00:57 +08:00
GyDi
5ec37c08bf v1.3.8 2023-10-31 01:29:20 +08:00
GyDi
84cb008421 chore: update log 2023-10-31 01:28:57 +08:00
GyDi
a744cca35a fix: add default valid key 2023-10-30 17:10:51 +08:00
GyDi
1950cf5f99 fix: page null exception, close #821 2023-10-30 00:53:24 +08:00
GyDi
fbf230cd01 feat: adjust the delay display interval and color, close #836 2023-10-29 23:01:05 +08:00
keiko233
05abf1e419 chore: update dependencies 2023-10-22 00:44:01 +08:00
keiko233
f0c13980ed chore: tsconfig: skip Lib Check 2023-10-21 23:53:57 +08:00
keiko233
d136207d2f feat: theme: change color 2023-10-21 17:30:50 +08:00
keiko233
b331ee5d93 chore: update README.md 2023-10-21 17:02:13 +08:00
keiko233
73cd6e981a feat: profiles: import btn with loading state 2023-10-21 16:50:46 +08:00
keiko233
6826be73c7 feat: profile-viewer: handleOk with loading state 2023-10-21 16:47:39 +08:00
keiko233
0a61a607c9 feat: base-dialog: okBtn use LoadingButton 2023-10-21 16:47:10 +08:00
keiko233
7444e1566b chore: import MUI Lab 2023-10-21 16:43:30 +08:00
keiko233
e401661cfc chore: Update dependencies 2023-10-21 16:43:16 +08:00
keiko233
89d32f7109 feat: Nyanpasu Misc 2023-10-20 11:52:40 +08:00
keiko233
5b4c88f933 feat: Theme support modify --background-color 2023-10-20 11:12:49 +08:00
keiko233
966bce973a feat: settings use Grid layout 2023-10-20 10:05:03 +08:00
keiko233
69cbcae7ed chore: Use your own update endpoints and public keys 2023-10-19 09:37:31 +08:00
keiko233
e26cf282d6 chore: readme remove ad 2023-10-18 13:38:58 +08:00
GyDi
6ec7d55cbc chore: fix rust lint 2023-10-18 13:38:03 +08:00
GyDi
9888f2a322 chore: fix rust lint 2023-10-11 14:55:20 +08:00
GyDi
15d29b2c79 chore: fix alpha ci 2023-10-11 14:31:44 +08:00
GyDi
7e768e0a17 fix: try fix csp 2023-10-11 14:21:56 +08:00
GyDi
affa41b5a9 chore: update readme 2023-10-11 00:04:56 +08:00
keiko233
c8cf179ed9 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
9ef7310fc2 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
cdd7d0f4c5 chore: update readme 2023-10-10 10:25:57 +08:00
GyDi
813d706dac chore: update readme 2023-10-10 10:25:01 +08:00
GyDi
223d4133c5 chore: update clash meta 2023-10-08 21:45:12 +08:00
GyDi
bb62ebc23e chore: update readme 2023-09-11 10:24:44 +08:00
GyDi
a4c2adf69a chore: fix updater 2023-09-11 10:16:54 +08:00
GyDi
0baff0a1e1 chore: fix updater 2023-09-11 10:12:40 +08:00
GyDi
51766ad72c chore: fix clash map, close #736 2023-09-10 19:06:02 +08:00
GyDi
bf00b42941 v1.3.7 2023-09-10 19:02:10 +08:00
GyDi
56482acfc2 chore: update log 2023-09-10 19:01:59 +08:00
GyDi
8898b1a608 chore: update auto launch 2023-09-10 18:45:17 +08:00
GyDi
525bc37649 fix: i18n 2023-09-10 15:03:29 +08:00
GyDi
9d29450f47 feat: add Open Dashboard to the hotkey, close #723 2023-09-10 14:46:03 +08:00
GyDi
26c98bdace feat: add check for updates button, close #766 2023-09-10 14:30:31 +08:00
GyDi
19ccd35f3f fix: fix page undefined exception, close #770 2023-09-10 13:45:18 +08:00
GyDi
efa7529c5c feat: add paste and clear icon 2023-09-09 16:52:00 +08:00
Majokeiko
334c11ccd1 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
c318a5dd79 chore: update mmdb 2023-08-28 15:06:13 +08:00
GyDi
25d1a8957d fix: set min window size, close #734 2023-08-28 15:00:27 +08:00
GyDi
9f4853b9d6 chore: change ubuntu ci version 2023-08-28 14:44:09 +08:00
GyDi
ac58f50b0a chore: update clash 2023-08-28 14:22:50 +08:00
GyDi
36f22b660e chore: update release link 2023-08-14 11:11:15 +08:00
GyDi
398046eca6 chore: update clash meta 2023-08-14 11:09:19 +08:00
GyDi
a6f2ce4bdd fix: rm debug code 2023-08-12 19:16:20 +08:00
GyDi
950a8ea8df v1.3.6 2023-08-12 16:12:03 +08:00
GyDi
2b6b979f88 chore: update log 2023-08-12 16:11:50 +08:00
GyDi
4b80e5ff47 fix: use sudo when pkexec not found 2023-08-12 15:58:37 +08:00
GyDi
a14d18c6b6 chore: update ci 2023-08-12 15:41:26 +08:00
GyDi
fe88c7ce97 feat: show loading when change profile 2023-08-05 22:07:30 +08:00
GyDi
c54b00a701 fix: remove div 2023-08-05 21:43:58 +08:00
GyDi
4a4b8c2e12 fix: list key 2023-08-05 21:43:05 +08:00
GyDi
f1770711cb feat: support proxy provider update 2023-08-05 21:38:44 +08:00
GyDi
94757f0f0c feat: add repo link 2023-08-05 19:54:59 +08:00
GyDi
15cf9be90d feat: support clash meta memory usage display 2023-08-05 19:40:23 +08:00
GyDi
cb1955c217 fix: websocket disconnect when window focus 2023-08-05 17:21:15 +08:00
GyDi
2ce944034d feat: supports show connection detail 2023-08-05 16:52:14 +08:00
GyDi
53a207e859 chore: update geo data to meta, close #707 2023-08-05 13:42:37 +08:00
GyDi
dbdd2411c3 chore: fix faq 2023-08-05 13:41:30 +08:00
GyDi
4ac3fbd726 chore: add faq and download link 2023-08-05 13:38:19 +08:00
GyDi
8f8f62555c chore: add promotion 2023-08-05 12:57:48 +08:00
whitemirror33
73b980c6a9 feat: update connection table with wider process column and click to show full detail (#696) 2023-08-04 14:36:28 +08:00
GyDi
d2cce3cc40 feat: more trace logs 2023-08-04 14:15:15 +08:00
Andrei Shevchuk
4139360788 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
ca7bb50212 fix: try fix undefined error 2023-07-28 09:26:06 +08:00
GyDi
48ebf626b8 chore: alpha ci 2023-07-26 17:07:20 +08:00
GyDi
564eb802b1 feat: center window when out of monitor 2023-07-24 20:55:26 +08:00
GyDi
c968949f49 chore: fix test ci 2023-07-24 09:59:13 +08:00
GyDi
74268ecce6 chore: fix test ci 2023-07-24 09:47:05 +08:00
GyDi
93f9db3af4 chore: test ci 2023-07-24 09:28:54 +08:00
GyDi
4cc0a9d4a9 chore: test ci 2023-07-24 09:13:19 +08:00
GyDi
a04dfba16b v1.3.5 2023-07-23 13:31:51 +08:00
GyDi
7f83b58b92 chore: update log 2023-07-23 13:31:34 +08:00
GyDi
ca2e6353ae fix: blurry tray icon in Windows 2023-07-23 13:25:54 +08:00
GyDi
6bf7795529 chore: update clash core 2023-07-23 13:11:35 +08:00
GyDi
401f4828b6 chore: fix check script 2023-07-23 13:11:17 +08:00
GyDi
7a0ce1efe7 chore: update issue templates 2023-07-22 23:04:13 +08:00
GyDi
cdc2550e34 v1.3.4 2023-07-22 20:38:08 +08:00
GyDi
2d760241f3 chore: update log 2023-07-22 20:37:22 +08:00
GyDi
3748e420a0 fix: enable context menu in editable element 2023-07-22 17:21:04 +08:00
GyDi
031a253101 feat: support copy environment variable 2023-07-22 15:35:32 +08:00
GyDi
cfce6d548b fix: save window size and pos in Windows 2023-07-22 13:13:16 +08:00
GyDi
038e93ea6a feat: save window size and position 2023-07-22 10:58:16 +08:00
GyDi
8028e145f1 feat: app log level add silent 2023-07-22 09:25:54 +08:00
GyDi
ddfb9fd0cc feat: overwrite resource file according to file modified 2023-07-22 09:18:54 +08:00
GyDi
a36ed6ab1e feat: support app log level settings 2023-07-22 08:53:37 +08:00
Kimiblock Moe
0c7fe0664e feat: Use polkit to elevate permission instaed of sudo (#678) 2023-07-21 23:05:33 +08:00
GyDi
7ae2f1980b chore: update check script 2023-07-11 13:25:55 +08:00
GyDi
0c89583b1b fix: optimize traffic graph high CPU usage when hidden 2023-07-10 23:44:09 +08:00
GyDi
f5ec43276a fix: remove fallback group select status, close #659 2023-07-10 23:16:19 +08:00
GyDi
e55e46011c chore: update clash 2023-07-10 13:36:17 +08:00
GyDi
77c0304faf feat: add unified-delay field 2023-06-30 13:58:51 +08:00
GyDi
ea476e26ae v1.3.3 2023-06-30 09:38:33 +08:00
GyDi
2eb47aef62 chore: update log 2023-06-30 09:35:49 +08:00
GyDi
3e4084d99c fix: error boundary with key 2023-06-30 09:17:59 +08:00
GyDi
955ac92043 chore: update clash meta 2023-06-30 09:02:26 +08:00
GyDi
5a65a07a39 fix: connections is null 2023-06-30 09:02:17 +08:00
GyDi
554d73c6ee fix: font family not works in some interfaces, close #639 2023-06-29 14:21:14 +08:00
GyDi
7bc7bc7c49 fix: encodeURIComponent secret 2023-06-29 14:15:57 +08:00
GyDi
7b8d47cdeb chore: update clash & clash meta 2023-06-08 13:52:40 +08:00
GyDi
63a8509f1f feat: add error boundary to the app root 2023-06-08 13:50:45 +08:00
GyDi
4e69454f72 fix: encode controller secret, close #601 2023-06-08 13:48:58 +08:00
GyDi
ec3e237093 fix: linux not change icon 2023-05-28 18:14:11 +08:00
GyDi
edd224a185 fix: try fix blank error 2023-05-28 17:35:00 +08:00
GyDi
b5142b8ef5 fix: close all connections when change mode 2023-05-28 17:07:39 +08:00
GyDi
ddfdf1d49d fix: macos not change icon 2023-05-28 16:46:17 +08:00
GyDi
e7675c29da chore: update deps 2023-05-28 16:45:43 +08:00
GyDi
0bf3fef431 chore: update clash 2023-05-28 16:44:07 +08:00
w568w
8cb0e81e89 feat: show tray icon variants in different status (#537) 2023-05-28 10:55:39 +08:00
GyDi
d468cb051d fix: error message null 2023-05-19 10:53:11 +08:00
GyDi
0b24be7532 chore: update gh proxy 2023-05-19 09:44:31 +08:00
GyDi
6316a0ede9 v1.3.2 2023-05-19 00:57:22 +08:00
GyDi
3c9d0e02f6 chore: update log 2023-05-19 00:57:10 +08:00
GyDi
02d4360526 fix: profile data undefined error, close #566 2023-05-18 20:02:46 +08:00
GyDi
aedf80e382 chore: update clash core 2023-05-05 20:45:52 +08:00
GyDi
51b492e1e2 chore: update deps 2023-05-05 20:27:46 +08:00
yettera765
d283f236db fix: import url error (#543)
use rustls instead of depending user's system tls
2023-05-05 12:08:08 +08:00
GyDi
371e937a0e v1.3.1 2023-04-11 10:04:55 +08:00
GyDi
5778c25477 chore: update log 2023-04-11 10:04:28 +08:00
Tatius Titus
d2cd3ec879 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
ba0a59629b fix: linux DEFAULT_BYPASS (#503) 2023-04-07 12:47:13 +08:00
GyDi
03fea7558d chore: update deps 2023-04-02 11:20:18 +08:00
GyDi
4901673c4e fix: open file with vscode 2023-04-02 11:19:48 +08:00
GyDi
a4d84bd2e8 chore: update deps 2023-04-02 10:09:14 +08:00
GyDi
0289218c92 chore: update clash meta 2023-04-02 09:43:32 +08:00
Tatius Titus
c3d32e59e7 fix: Do not render div as a descendant of p (#494) 2023-04-02 09:37:09 +08:00
Srinivas Gowda
8cab08351b chore: update clash core version (#476) 2023-03-27 10:59:27 +08:00
GyDi
5de0e58d5d fix: use replace instead 2023-03-17 08:37:45 +08:00
GyDi
438106f42e fix: escape path space 2023-03-17 08:36:19 +08:00
John Smith
6b21f38047 fix: escape the space in path (#451) 2023-03-17 08:20:35 +08:00
GyDi
1b07a5f3c0 fix: add target os linux 2023-03-16 23:57:34 +08:00
GyDi
a39ec5a061 chore: fix ci 2023-03-16 23:54:14 +08:00
GyDi
015df9e6dd fix: appimage path unwrap panic 2023-03-16 23:45:48 +08:00
GyDi
bf95284b72 v1.3.0 2023-03-16 21:33:51 +08:00
GyDi
07c04f0c2f chore: update log 2023-03-16 21:33:14 +08:00
GyDi
b9976ee68e feat: auto restart core after grand permission 2023-03-16 21:32:39 +08:00
GyDi
c8d9951ae4 feat: add restart core button 2023-03-16 20:56:37 +08:00
GyDi
97339512e1 fix: remove esc key listener in macOS 2023-03-16 20:42:45 +08:00
GyDi
95682a7a0b feat: support update all profiles 2023-03-16 20:32:41 +08:00
GyDi
87038c4c75 chore: rm file 2023-03-16 20:31:48 +08:00
GyDi
1e5b5193fe fix: adjust style 2023-03-16 19:32:59 +08:00
hybo
9cc47678a2 chore: evaluate error context lazily (#447) 2023-03-16 17:06:52 +08:00
GyDi
198109cd43 fix: adjust swr option 2023-03-16 17:03:12 +08:00
GyDi
16490541e4 fix: infinite retry when websocket error 2023-03-16 17:03:00 +08:00
GyDi
6d5ca25e03 fix: type error 2023-03-16 13:51:46 +08:00
GyDi
0ca44bd859 chore: update deps 2023-03-16 13:42:27 +08:00
GyDi
124b5b0549 chore: fix ci 2023-03-16 13:18:05 +08:00
GyDi
fed62fae3c chore: fix ci 2023-03-16 13:16:30 +08:00
GyDi
9e2812d55c feat: support to grant permission to clash core 2023-03-16 11:16:54 +08:00
GyDi
fdcbc3904a fix: do not parse log except the clash core 2023-03-16 10:34:28 +08:00
GyDi
cc0114cd90 feat: support clash fields filter in ui 2023-03-15 08:43:24 +08:00
GyDi
f97753c1d0 fix: field sort for filter 2023-03-15 08:43:03 +08:00
boatrainlsz
fcdf545a3c chore: update README (#435) 2023-03-07 22:55:38 +08:00
GyDi
bf28f887eb chore: update deps 2023-03-07 00:10:49 +08:00
GyDi
5f1f04e486 chore: update clash 2023-03-06 23:35:30 +08:00
GyDi
2242174749 fix: add meta fields 2023-02-18 00:46:03 +08:00
GyDi
e8af077fe2 chore: update clash 2023-02-18 00:09:40 +08:00
GyDi
055252f746 feat: open dir on the tray 2023-02-17 00:15:21 +08:00
GyDi
8dc84d7c17 fix: runtime config user select 2023-02-17 00:00:23 +08:00
GyDi
f140c75fb9 feat: support to disable clash fields filter 2023-02-16 23:52:55 +08:00
GyDi
c63dd46bac fix: app_handle as_ref 2023-02-15 17:25:35 +08:00
GyDi
a23b0ba945 fix: use crate 2023-02-11 23:31:55 +08:00
GyDi
7e768aa6e9 fix: appimage auto launch, close #403 2023-02-11 23:19:08 +08:00
inRm3D
18765508c0 chore: add openssl depend (#395) 2023-02-02 10:54:15 +08:00
GyDi
efb3464ee2 v1.2.3 2023-02-01 22:11:34 +08:00
GyDi
f3319c5f75 chore: update log 2023-02-01 22:11:06 +08:00
GyDi
384623fbb7 chore: aarch script 2023-02-01 21:41:15 +08:00
GyDi
3bcccdf3dd chore: update clash 2023-01-30 20:50:08 +08:00
GyDi
d9b913fa69 chore: meta ci 2023-01-30 20:40:57 +08:00
GyDi
7b9a430e8a chore: fix ci 2023-01-22 13:31:34 +08:00
GyDi
4f3ab0dc69 chore: fix ci 2023-01-19 18:02:08 +08:00
GyDi
427caaf9aa chore: alpha ci 2023-01-18 23:39:27 +08:00
inRm3D
85eefdebbb chore: try fix missing libssl3 (#378)
* Update ci.yml

* use package from source
2023-01-18 23:36:18 +08:00
GyDi
b31b70302b fix: compatible with UTF8 BOM, close #283 2023-01-17 19:51:02 +08:00
GyDi
c830ea676f feat: adjust macOS window style 2023-01-16 22:57:53 +08:00
GyDi
9249059cb7 fix: use selected proxy after profile changed 2023-01-15 21:33:03 +08:00
GyDi
9fee228d1a fix: error log 2023-01-15 19:11:51 +08:00
GyDi
20718c6ec1 chore: ci 2023-01-14 22:22:18 +08:00
GyDi
b8754f3f44 v1.2.2 2023-01-14 22:01:28 +08:00
GyDi
6f598ae59e chore: update log 2023-01-14 21:56:27 +08:00
GyDi
ca81fcaf37 fix: adjust fields order 2023-01-14 14:57:55 +08:00
GyDi
75867da94f fix: add meta fields 2023-01-14 14:51:29 +08:00
GyDi
53c11701de chore: ci 2023-01-14 12:20:17 +08:00
GyDi
4741793bb6 chore: update clash meta 2023-01-14 12:16:09 +08:00
GyDi
ab161a42ee fix: add os platform value 2023-01-14 12:07:31 +08:00
GyDi
4642b79b5b feat: recover core after panic, close #353 2023-01-14 11:45:47 +08:00
GyDi
1cb43fe110 chore: allow unused 2023-01-13 23:11:48 +08:00
GyDi
0123135237 fix: reconnect traffic websocket 2023-01-13 23:02:45 +08:00
GyDi
1d77f91acc chore: update deps 2023-01-13 21:29:21 +08:00
GyDi
9b89333b1b feat: use decorations in Linux, close #354 2023-01-12 00:42:33 +08:00
GyDi
829361d767 fix: parse bytes precision, close #334 2023-01-11 14:05:49 +08:00
GyDi
d3fca26499 fix: trigger new profile dialog, close #356 2023-01-11 13:45:16 +08:00
GyDi
ff3460e100 fix: parse log cause panic 2023-01-11 13:30:14 +08:00
GyDi
1a209a54c6 chore: update clash meta 2023-01-09 21:55:03 +08:00
GyDi
3bf0ac087f v1.2.1 2022-12-23 22:44:27 +08:00
GyDi
96cf6215ce chore: update log 2022-12-23 22:44:00 +08:00
GyDi
71ce6120c3 fix: avoid setting login item repeatedly, close #326 2022-12-23 22:39:27 +08:00
GyDi
21e82d0daa fix: adjust code 2022-12-15 21:54:48 +08:00
GyDi
8c20bb003b fix: adjust delay check concurrency 2022-12-15 12:23:57 +08:00
GyDi
90913d2486 chore: adjust code 2022-12-15 12:22:20 +08:00
GyDi
ef0de04a0f fix: change default column to auto 2022-12-14 16:56:33 +08:00
GyDi
8879a0ce8a fix: change default app version 2022-12-14 16:15:53 +08:00
GyDi
18db46800e fix: adjust rule ui 2022-12-14 15:59:41 +08:00
GyDi
955c5b5605 fix: adjust log ui 2022-12-14 15:49:05 +08:00
GyDi
c3b2c88213 fix: keep delay data 2022-12-14 15:29:02 +08:00
GyDi
7412bb35ad fix: use list item button 2022-12-14 15:16:49 +08:00
GyDi
2a6fbc5c5d fix: proxy item style 2022-12-14 15:15:44 +08:00
GyDi
90c9b87f8d feat: auto proxy layout column 2022-12-14 15:07:51 +08:00
GyDi
ffe2557e84 chore: fix ci 2022-12-13 18:13:28 +08:00
GyDi
a77798e5b4 chore: fix ci 2022-12-13 18:11:43 +08:00
GyDi
c1fa7bfce6 chore: fix ci 2022-12-13 18:02:23 +08:00
GyDi
bf50da1e6b chore: alpha ci 2022-12-13 17:44:46 +08:00
GyDi
2cd4aac5ce chore: alpha ci 2022-12-13 17:41:53 +08:00
GyDi
208b96c092 feat: support to change proxy layout column 2022-12-13 17:34:39 +08:00
GyDi
36a53f8134 feat: support to open core dir 2022-12-13 00:44:24 +08:00
GyDi
e8d8a8737e chore: update deps 2022-12-12 00:00:45 +08:00
MoeShin
41b9f90a0b fix: Virtuoso no work in legacy browsers (#318) 2022-12-08 10:47:42 +08:00
GyDi
5e39493e89 chore: update deps 2022-12-04 20:54:43 +08:00
GyDi
31f3c60401 fix: adjust ui 2022-12-04 00:45:39 +08:00
GyDi
519c74e4dd feat: profile page ui 2022-11-28 22:29:58 +08:00
GyDi
4419770b15 chore: deps 2022-11-28 22:27:23 +08:00
GyDi
b7fa86c848 chore: update clash version 2022-11-26 13:25:52 +08:00
GyDi
4d620c3db9 fix: refresh websocket 2022-11-25 22:22:57 +08:00
GyDi
319c8cb54c fix: adjust ui 2022-11-24 18:24:34 +08:00
GyDi
868b6f141c feat: save some fields in the runtime config, close #292 2022-11-24 17:52:25 +08:00
GyDi
589fdd4cfe chore: alpha ci 2022-11-24 11:14:03 +08:00
GyDi
3f36dd9a14 fix: parse bytes base 1024 2022-11-24 11:11:31 +08:00
GyDi
0a6568bbab fix: add clash fields 2022-11-24 10:46:21 +08:00
GyDi
4e26d56746 chore: ci 2022-11-24 10:41:03 +08:00
GyDi
d4e363fbd7 chore: ci 2022-11-24 10:35:22 +08:00
GyDi
b721b822eb fix: direct mode hide proxies 2022-11-24 10:35:09 +08:00
GyDi
58d6985080 feat: add meta feature 2022-11-24 10:26:41 +08:00
GyDi
ee2abd415e fix: profile can not edit 2022-11-24 10:26:25 +08:00
GyDi
09c9321159 chore: ci 2022-11-23 23:46:32 +08:00
GyDi
0900a7cf80 v1.2.0 2022-11-23 23:15:39 +08:00
GyDi
d3ec7f65fb chore: ci 2022-11-23 23:15:20 +08:00
GyDi
b24a94d6ce chore: update log 2022-11-23 23:13:44 +08:00
GyDi
65769c7766 feat: display proxy group type 2022-11-23 18:27:57 +08:00
GyDi
cbeef9fe06 chore: rm dead code 2022-11-23 17:45:49 +08:00
GyDi
b199209d0d fix: parse logger time 2022-11-23 17:45:43 +08:00
GyDi
cb48545600 fix: adjust service mode ui 2022-11-23 17:45:22 +08:00
GyDi
5e626e2cc5 feat: add use clash hook 2022-11-23 17:44:40 +08:00
GyDi
2709d1ff6e fix: adjust style 2022-11-23 17:42:01 +08:00
GyDi
de3ca6e237 fix: check hotkey and optimize hotkey input, close #287 2022-11-23 17:30:19 +08:00
GyDi
c2253e868c fix: mutex dead lock 2022-11-23 16:04:25 +08:00
GyDi
3a77c6f7eb fix: adjust item ui 2022-11-23 15:29:42 +08:00
GyDi
8e3ff7670b fix: regenerate config before change core 2022-11-23 09:57:21 +08:00
GyDi
7870147c16 fix: close connections when profile change 2022-11-23 00:20:57 +08:00
GyDi
64a371e5d8 fix: lint 2022-11-22 23:02:18 +08:00
GyDi
f837736a20 chore: fix check script 2022-11-22 23:01:04 +08:00
GyDi
dec503da81 fix: windows service mode 2022-11-22 22:01:34 +08:00
GyDi
1328299fda fix: init config file 2022-11-22 22:01:19 +08:00
GyDi
05190a52f1 chore: fix mmdb url 2022-11-22 21:15:45 +08:00
GyDi
46cc3105c4 fix: service mode error and fallback to sidecar 2022-11-22 20:47:21 +08:00
GyDi
8ba5853cdd fix: service mode viewer ui 2022-11-22 20:45:17 +08:00
GyDi
e5fbcc4a8c chore: update check script 2022-11-22 20:44:44 +08:00
GyDi
e559194e8e fix: create theme error, close #294 2022-11-22 16:01:33 +08:00
GyDi
c0d2994b8e feat: guard the mixed-port and external-controller 2022-11-22 15:45:17 +08:00
GyDi
89009aa1f8 feat: adjust builtin script and support meta guard script 2022-11-22 12:00:48 +08:00
MoeShin
ee8b580a98 fix: matchMedia().addEventListener #258 (#296) 2022-11-22 09:25:39 +08:00
GyDi
f4656fa493 chore: alpha release add portable 2022-11-22 00:16:30 +08:00
GyDi
adf9405fd7 chore: alpha ci 2022-11-21 23:19:25 +08:00
GyDi
f46db7ce1a fix: check config 2022-11-21 23:11:56 +08:00
GyDi
f00d726347 fix: show global when no rule groups 2022-11-21 23:06:32 +08:00
GyDi
e8d8063ebc fix: service viewer ref 2022-11-21 23:02:48 +08:00
GyDi
b4bc4f5ddc fix: service ref error 2022-11-21 22:33:06 +08:00
GyDi
0d3ffe210f feat: disable script mode when use clash meta 2022-11-21 22:28:57 +08:00
GyDi
8eb88a161a feat: check config when change core 2022-11-21 22:27:55 +08:00
GyDi
346c964419 fix: group proxies render list is null 2022-11-21 22:10:24 +08:00
GyDi
bc8be2460f feat: support builtin script for enhanced mode 2022-11-21 21:05:00 +08:00
GyDi
a33f24d19c chore: update ci 2022-11-20 23:17:05 +08:00
GyDi
8f839fbf8e chore: update ci node version 2022-11-20 23:14:43 +08:00
GyDi
10dd9101cd fix: pretty bytes 2022-11-20 23:08:30 +08:00
GyDi
aa5d3af8a1 feat: adjust profiles page ui 2022-11-20 22:37:34 +08:00
GyDi
dace993c21 refactor: adjust base components export 2022-11-20 22:03:55 +08:00
GyDi
32b72f0ef6 refactor: adjust setting dialog component 2022-11-20 21:48:39 +08:00
GyDi
3dbc54c8ae fix: use verge hook 2022-11-20 20:12:58 +08:00
GyDi
9dd3b8fd68 feat: optimize proxy page ui 2022-11-20 19:46:16 +08:00
GyDi
5fb1afc681 chore: adjust type 2022-11-19 17:22:29 +08:00
GyDi
a4c985a219 fix: adjust notice 2022-11-19 01:24:07 +08:00
GyDi
a1f819a458 feat: add error boundary 2022-11-19 01:22:19 +08:00
GyDi
166d7ba1cf chore: rm polyfill 2022-11-19 01:22:00 +08:00
GyDi
a089accd23 feat: adjust clash log 2022-11-18 22:08:06 +08:00
GyDi
38546e557f fix: windows issue 2022-11-18 20:15:34 +08:00
GyDi
080dca7ee0 fix: change dev log level 2022-11-18 18:37:29 +08:00
GyDi
a89e433f3d fix: patch clash config 2022-11-18 18:37:17 +08:00
GyDi
f30ef61b06 fix: cmds params 2022-11-18 18:26:55 +08:00
GyDi
03a36c1100 Merge branch 'refactor' 2022-11-18 18:21:49 +08:00
GyDi
546eb6e73e chore: rm code 2022-11-18 18:19:26 +08:00
GyDi
bedd3abf8a refactor: done 2022-11-18 18:18:41 +08:00
GyDi
ce2d4498e1 refactor: adjust all path methods and reduce unwrap 2022-11-18 10:26:39 +08:00
GyDi
a786023160 fix: adjust singleton detect 2022-11-18 08:24:27 +08:00
GyDi
98cf8aa445 fix: change template 2022-11-18 08:04:26 +08:00
GyDi
d733d9fd4c refactor: rm code 2022-11-17 23:21:13 +08:00
GyDi
58e9cb8b93 refactor: fix 2022-11-17 22:53:41 +08:00
GyDi
bb669acf95 refactor: rm dead code 2022-11-17 22:52:22 +08:00
GyDi
4f3751b7ce refactor: for windows 2022-11-17 20:19:40 +08:00
GyDi
84c12dee80 refactor: wip 2022-11-17 17:07:13 +08:00
GyDi
f5f865a139 refactor: wip 2022-11-16 01:26:41 +08:00
GyDi
902aed671a refactor: wip 2022-11-15 01:33:50 +08:00
GyDi
1423fe7e16 refactor: rm update item block_on 2022-11-14 23:07:51 +08:00
GyDi
58ed2e6f33 refactor: fix 2022-11-14 22:50:47 +08:00
GyDi
a90939f46a fix: copy resource file 2022-11-14 21:11:42 +08:00
GyDi
db7456b9c5 chore: tmpl add clash core 2022-11-14 21:10:29 +08:00
GyDi
a964b30c34 feat: add draft 2022-11-14 19:31:22 +08:00
GyDi
d566629d51 refactor: fix 2022-11-14 01:45:58 +08:00
GyDi
837422fbb8 refactor: wip 2022-11-14 01:26:33 +08:00
GyDi
afc37c71a6 fix: MediaQueryList addEventListener polyfill 2022-11-13 10:27:26 +08:00
GyDi
e8b014ea6d chore: fix ci 2022-11-12 17:07:47 +08:00
GyDi
b124512020 chore: alpha ci 2022-11-12 17:06:26 +08:00
GyDi
158f352328 chore: lock version 2022-11-12 17:06:18 +08:00
GyDi
f1895e32fb chore: fix ci 2022-11-12 16:54:57 +08:00
GyDi
6c3be01093 chore: alpha ci 2022-11-12 16:47:27 +08:00
GyDi
ebe548438c fix: change default tun dns-hijack 2022-11-12 11:48:02 +08:00
GyDi
a45c61f19e chore: format rust code 2022-11-12 11:37:23 +08:00
GyDi
b07a4b95aa fix: something 2022-11-11 22:45:32 +08:00
GyDi
777b4e13e6 chore: update deps 2022-11-11 21:28:34 +08:00
GyDi
92cfc9324d feat: change default latency test url 2022-11-11 01:24:18 +08:00
GyDi
97dfa18b9b fix: provider proxy sort by delay 2022-11-11 01:21:23 +08:00
GyDi
515c07ea2b chore: rm dead code 2022-11-10 22:58:46 +08:00
GyDi
4d17e45f86 chore: clash meta compatible and geosite.dat 2022-11-10 22:58:34 +08:00
GyDi
32095daf90 feat: auto close connection when proxy changed 2022-11-10 01:27:05 +08:00
GyDi
aa23ed892b fix: profile item menu ui dense 2022-11-09 15:53:42 +08:00
GyDi
3f079c8501 fix: disable auto scroll to proxy 2022-11-09 11:44:09 +08:00
GyDi
0aaf4bfde8 feat: support to change external controller 2022-11-06 23:23:26 +08:00
GyDi
b967cfc775 feat: add sub-rules 2022-11-05 15:50:06 +08:00
GyDi
2ca0483bf4 feat(macOS): support cmd+w and cmd+q 2022-11-04 00:51:46 +08:00
GyDi
03cedc7b35 chore: update clash meta 2022-11-04 00:36:32 +08:00
GyDi
35049b5830 chore: fix ci 2022-11-03 01:34:23 +08:00
GyDi
b707dde4da chore: compatible ci 2022-11-03 01:32:16 +08:00
GyDi
1ec8339b13 chore: update ci 2022-11-03 01:15:50 +08:00
GyDi
8bbe04a174 chore: test ci 2022-11-03 00:51:49 +08:00
GyDi
0b6a173ba0 chore: test ci 2022-11-03 00:48:27 +08:00
GyDi
8b41dfe94f chore: add x 2022-11-02 00:57:27 +08:00
GyDi
a18b1ac99b v1.1.2 2022-11-02 00:54:51 +08:00
GyDi
e007bcb640 fix: check remote profile 2022-11-02 00:51:25 +08:00
GyDi
1090e7ceae chore: update log 2022-11-02 00:44:45 +08:00
GyDi
933035dd7e feat: add version on tray 2022-11-01 23:29:59 +08:00
GyDi
aaaac78170 feat: add animation 2022-10-29 23:36:10 +08:00
GyDi
b5d2392def fix: remove smoother 2022-10-29 20:05:55 +08:00
angrylid
b88a736735 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
f238da416b fix: icon button color 2022-10-29 19:22:15 +08:00
GyDi
10c7e75491 feat: check remote profile field 2022-10-28 13:00:52 +08:00
GyDi
60af0735f4 fix: init system proxy correctly 2022-10-28 01:33:21 +08:00
GyDi
273cbf333e fix: open file 2022-10-28 01:26:45 +08:00
GyDi
189b17ad8f fix: reset proxy 2022-10-28 01:06:54 +08:00
GyDi
1eecf26429 fix: init config error 2022-10-28 01:02:47 +08:00
GyDi
2f9a3fa942 feat: system tray support zh language 2022-10-28 00:40:29 +08:00
GyDi
f166fb132e fix: adjust reset proxy 2022-10-27 21:14:14 +08:00
GyDi
0adb69139e fix: adjust code 2022-10-27 21:13:27 +08:00
GyDi
ab309e4b90 fix: add https proxy 2022-10-26 17:11:54 +08:00
GyDi
532c6d4fa5 fix: auto scroll into view when sorted proxies changed 2022-10-26 01:24:06 +08:00
GyDi
14f627d0d3 feat: display delay check result timely 2022-10-26 01:11:02 +08:00
GyDi
947c38c124 fix: refresh proxies interval, close #235 2022-10-26 01:08:34 +08:00
GyDi
f991e49203 fix: style 2022-10-25 23:54:21 +08:00
GyDi
4fbb6ed4ff chore: update deps 2022-10-25 23:54:10 +08:00
GyDi
2fa49303de fix: fetch profile with system proxy, close #249 2022-10-25 23:45:24 +08:00
LooSheng
f9527e9d2d fix: The profile is replaced when the request fails. (#246) 2022-10-23 17:22:26 +08:00
GyDi
80c63cb9d6 fix: default dns config 2022-10-22 22:26:40 +08:00
GyDi
a2455eeade fix: kill clash when exit in service mode, close #241 2022-10-22 13:55:06 +08:00
GyDi
5d7ad4b3bd chore: update mmdb 2022-10-22 13:30:08 +08:00
GyDi
f46fa61cf3 feat: update profile with system proxy/clash proxy 2022-10-18 23:19:21 +08:00
GyDi
da24a9637b chore: adjust code 2022-10-17 23:26:25 +08:00
GyDi
2cdc11c2ad fix: icon button color inherit 2022-10-16 14:40:45 +08:00
GyDi
191cd3f25c fix: app version to string 2022-10-16 14:38:57 +08:00
GyDi
f158bce8bd fix: break loop when core terminated 2022-10-14 14:46:15 +08:00
GyDi
94eebb2dd6 feat: change global mode ui, close #226 2022-10-13 11:54:52 +08:00
GyDi
375b146690 feat: default user agent same with app version 2022-10-12 11:28:47 +08:00
GyDi
8c13214ff2 v1.1.1 2022-10-11 23:03:12 +08:00
GyDi
681dfadb57 chore: update log 2022-10-11 23:02:49 +08:00
GyDi
b8b5d1cb09 fix: api error handle 2022-10-11 22:51:18 +08:00
GyDi
e187dd86ed fix: clash meta not load geoip, close #212 2022-10-11 22:24:54 +08:00
GyDi
c380d6988c fix: sort proxy during loading, close #221 2022-10-11 22:06:55 +08:00
GyDi
444acc8ef5 fix: not create windows when enable slient start 2022-10-11 21:50:00 +08:00
GyDi
068f08aa45 fix: root background color 2022-10-11 20:49:03 +08:00
GyDi
bdc101f69c fix: create window correctly 2022-10-11 00:57:34 +08:00
GyDi
0ece530b9e fix: set_activation_policy 2022-10-10 22:15:21 +08:00
GyDi
5853a86091 chore: rm aarch64 ci 2022-09-28 18:50:33 +08:00
GyDi
20ba87bc88 chore: test ci 2022-09-28 17:26:32 +08:00
GyDi
be166ec158 chore: test ci 2022-09-28 17:12:27 +08:00
GyDi
02147891c2 chore: fix ci 2022-09-28 16:04:05 +08:00
GyDi
c08cd588eb chore: fix test ci 2022-09-28 15:19:50 +08:00
Particle_G
882bb564b1 chore: add support for windows arm64, close #216 (#209) 2022-09-28 15:13:24 +08:00
GyDi
18edf06a20 fix: disable spell check 2022-09-28 14:15:22 +08:00
苏业钦
68b52b6130 chore: add support linux arm64 (#215) 2022-09-28 10:40:41 +08:00
GyDi
3503f3eb4b chore: clash meta linux use compatible version 2022-09-27 00:16:22 +08:00
GyDi
a75706f329 feat: optimize config feedback 2022-09-26 20:46:29 +08:00
GyDi
29fe9d973c fix: adjust init launch on dev 2022-09-26 01:35:19 +08:00
GyDi
87d3320fa1 v1.1.0 2022-09-26 01:27:56 +08:00
GyDi
0b96dbc04c chore: update log 2022-09-26 01:27:32 +08:00
GyDi
55ed58c4a0 fix: ignore disable auto launch error 2022-09-26 01:09:05 +08:00
GyDi
8503e8c9ad fix(macos): set auto launch path to application 2022-09-25 23:36:55 +08:00
GyDi
fcdf783c40 fix: i18n 2022-09-25 01:41:46 +08:00
GyDi
008b92acb2 feat: show connections with table layout 2022-09-25 01:35:21 +08:00
GyDi
baf8ef8516 fix: style 2022-09-24 20:55:40 +08:00
GyDi
0cf89467e3 fix: save enable log on localstorage 2022-09-24 19:03:14 +08:00
Priestch
37d8a563c0 fix: typo in api.ts (#207) 2022-09-24 18:48:11 +08:00
GyDi
f3de93d73a chore: use ubuntu latest 2022-09-24 16:08:39 +08:00
GyDi
4de23bd160 feat: show loading on proxy group delay check 2022-09-24 15:31:09 +08:00
GyDi
29397ca04f fix: refresh clash ui await patch 2022-09-24 14:01:28 +08:00
Shun Li
6038a36532 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
186a922d06 refactor(hotkey): use tauri global shortcut 2022-09-23 15:31:01 +08:00
GyDi
be1f9e66e3 feat: adjust connection page ui 2022-09-23 00:08:52 +08:00
GyDi
9ac015e550 Merge pull request #197 from FoundTheWOUT/main
Support ordering connection
2022-09-22 22:55:46 +08:00
GyDi
f7a9ac0ba2 feat: yaml merge key 2022-09-21 22:15:24 +08:00
GyDi
39a8ddcfec feat: toggle log ws 2022-09-20 22:15:28 +08:00
GyDi
3a9e28ba48 chore: update deps 2022-09-20 21:26:38 +08:00
GyDi
b6a479355e feat: add rule page 2022-09-18 23:19:02 +08:00
GyDi
a791c964e8 feat: hotkey viewer 2022-09-18 15:52:53 +08:00
GyDi
9686a7f9bf feat: refresh ui when hotkey clicked 2022-09-18 15:50:03 +08:00
FoundTheWOUT
6b65b89350 Support ordering connection 2022-09-15 15:52:12 +08:00
GyDi
09a1915ec6 feat: support hotkey (wip) 2022-09-14 01:19:02 +08:00
GyDi
3424c421a4 fix: remove dead code 2022-09-12 13:42:21 +08:00
GyDi
77b89b6841 fix: style 2022-09-12 13:42:13 +08:00
GyDi
09807cfcad fix: handle is none 2022-09-12 00:45:19 +08:00
GyDi
6ca81df40c fix: unused 2022-09-12 00:02:25 +08:00
GyDi
2d00ddad2b refactor: optimize 2022-09-11 20:58:55 +08:00
GyDi
04f4934adb fix: style 2022-09-11 18:38:51 +08:00
GyDi
0593007fd0 fix: windows logo size 2022-09-11 17:50:58 +08:00
GyDi
c07cbf9dbb feat: hide window on macos 2022-09-09 16:42:35 +08:00
GyDi
ddd60aa1ff fix: do not kill sidecar during updating 2022-09-09 16:33:04 +08:00
GyDi
b5ecdbbc48 fix: delay update config 2022-09-09 16:30:27 +08:00
GyDi
0cca613b04 fix: reduce logo size 2022-09-09 16:19:13 +08:00
GyDi
b254ad177a feat: system proxy setting 2022-09-07 01:51:43 +08:00
GyDi
4fd9213d2c fix: window center 2022-09-06 22:18:06 +08:00
GyDi
982d1e219c chore: update resource 2022-09-06 15:32:22 +08:00
GyDi
3923b8631d fix: log level warn value 2022-09-06 14:42:58 +08:00
GyDi
72dcdf4483 feat: change default singleton port and support to change the port 2022-09-06 00:45:01 +08:00
GyDi
5f3a71ed5f feat: log info 2022-09-05 20:30:39 +08:00
GyDi
000d6ebcd0 feat: kill clash by pid 2022-09-05 16:30:29 +08:00
GyDi
7f0a04e3e3 feat: change clash port in dialog 2022-09-05 02:12:25 +08:00
GyDi
005c6bb9f1 feat: add proxy item check loading 2022-09-04 23:53:48 +08:00
GyDi
59c856fc0e feat: compatible with proxy providers health check 2022-09-04 22:55:54 +08:00
GyDi
5bf26619ee v1.0.6 2022-09-03 00:40:50 +08:00
GyDi
676c1d560b chore: update log 2022-09-03 00:39:39 +08:00
GyDi
5afaa7c079 chore: update deps 2022-09-02 23:06:00 +08:00
GyDi
b0b49b72b8 chore: update deps 2022-09-02 01:35:06 +08:00
GyDi
e7600458a9 chore: use ubuntu 18.04 2022-09-02 01:27:06 +08:00
GyDi
e0fa308984 feat: add empty ui 2022-09-02 01:09:38 +08:00
GyDi
6ce6fed10d feat: complete i18n 2022-09-02 01:05:45 +08:00
GyDi
b23ca2e138 fix: increase delay checker concurrency 2022-09-02 00:41:43 +08:00
GyDi
2798dc3d42 fix: external controller allow lan 2022-09-02 00:24:19 +08:00
GyDi
0586d4d7a9 chore: update clash meta version 2022-09-02 00:11:52 +08:00
GyDi
d2b3a52424 fix: remove useless optimizations 2022-08-31 21:44:23 +08:00
GyDi
ffb97242d9 chore: test ci 2022-08-29 17:26:09 +08:00
GyDi
13ab400d6d chore: update clash 2022-08-29 11:11:55 +08:00
GyDi
db6d47b3f3 chore: add test ci os version 2022-08-29 11:02:14 +08:00
GyDi
393943bb9f Merge branch 'main' of github.com:zzzgydi/clash-verge 2022-08-24 22:41:36 +08:00
GyDi
4c72ac14e6 chore: update auto launch 2022-08-24 22:41:12 +08:00
GyDi
7156871412 fix: reduce unsafe unwrap 2022-08-23 15:19:04 +08:00
FoundTheWOUT
20665a49ed fix: timer restore at app launch 2022-08-23 10:35:36 +08:00
GyDi
ad1dfc3137 fix: adjust log text 2022-08-19 17:11:59 +08:00
GyDi
8de67411ac fix: only script profile can display console 2022-08-17 01:45:37 +08:00
GyDi
2e75c9951b v1.0.5 2022-08-16 23:56:29 +08:00
GyDi
77f66f44a4 chore: update log 2022-08-16 23:56:04 +08:00
GyDi
3278a397f4 fix: fill button title attr 2022-08-16 23:46:33 +08:00
GyDi
d1c2abbaf3 feat: windows portable version do not check update 2022-08-16 01:53:40 +08:00
GyDi
8a443013c0 fix: do not reset system proxy when consistent 2022-08-16 01:27:32 +08:00
GyDi
d3735c4763 fix: adjust web ui item style 2022-08-16 01:08:54 +08:00
GyDi
f9887434d1 fix: clash field state error 2022-08-16 00:48:06 +08:00
GyDi
12afa005d9 feat: adjust clash info parsing logs 2022-08-15 20:21:43 +08:00
GyDi
7c0129b911 fix: badge color error 2022-08-15 20:15:44 +08:00
GyDi
332f92d08e fix: web ui port value error 2022-08-15 20:14:33 +08:00
GyDi
281ac16dca fix: delay show window 2022-08-15 01:37:26 +08:00
GyDi
e8a2b9446d feat: adjust runtime config 2022-08-15 01:30:37 +08:00
GyDi
4e33f3e722 feat: support restart app on tray 2022-08-15 01:22:39 +08:00
GyDi
e46e60153d fix: adjust dialog action button variant 2022-08-15 00:55:35 +08:00
GyDi
d3559bb1b7 feat: optimize profile page 2022-08-14 23:10:19 +08:00
GyDi
c8a046996b fix: script code error 2022-08-12 23:41:25 +08:00
GyDi
6c85b8717f fix: script exception handle 2022-08-12 11:14:34 +08:00
GyDi
142a62e371 feat: refactor 2022-08-12 03:20:55 +08:00
GyDi
ff6abf08b7 feat: adjust tun mode config 2022-08-11 03:26:08 +08:00
GyDi
2fd921cd60 feat: reimplement enhanced mode 2022-08-11 02:55:10 +08:00
GyDi
1d7bd57357 feat: use rquickjs crate 2022-08-10 13:05:48 +08:00
GyDi
1a4d76b4c3 feat: reimplement enhanced mode 2022-08-09 21:15:55 +08:00
GyDi
853d869086 chore: format 2022-08-09 14:33:47 +08:00
GyDi
c99d669b06 fix: change fields 2022-08-09 14:31:59 +08:00
FoundTheWOUT
19d3921637 fix: silent start (#150) 2022-08-09 14:14:06 +08:00
GyDi
4ee716457f fix: save profile when update 2022-08-08 23:17:22 +08:00
GyDi
5fb19bb187 fix: list compare wrong 2022-08-08 23:16:28 +08:00
GyDi
8b83c5349d fix: button color 2022-08-08 23:15:05 +08:00
GyDi
ac91942452 chore: rm code 2022-08-08 22:30:13 +08:00
GyDi
5ef23ddc1d fix: limit theme mode value 2022-08-08 22:28:44 +08:00
GyDi
2ad6c66a8d feat: finish clash field control 2022-08-08 22:14:03 +08:00
GyDi
630f877e95 feat: clash field viewer wip 2022-08-08 01:51:30 +08:00
GyDi
7dd5ce1356 fix: add valid clash field 2022-08-07 20:52:50 +08:00
GyDi
54b18d15b6 feat: support web ui 2022-08-06 21:56:54 +08:00
GyDi
2b0880af80 feat: adjust setting page style 2022-08-06 03:48:03 +08:00
GyDi
786ea17f95 refactor: ts path alias 2022-08-06 02:35:11 +08:00
GyDi
c699bae99c fix: icon style 2022-08-06 01:44:33 +08:00
GyDi
588e8e019c fix: reduce unwrap 2022-07-30 23:32:52 +08:00
GyDi
1e7a86b7c0 feat: runtime config viewer 2022-07-25 01:20:13 +08:00
GyDi
2bb18b18b7 chore: fix updater script 2022-07-18 12:55:59 +08:00
GyDi
da77c549a7 v1.0.4 2022-07-17 17:56:07 +08:00
GyDi
fc707c157a chore: update log 2022-07-17 17:52:41 +08:00
GyDi
5be6f550be feat: improve log rule 2022-07-17 17:39:44 +08:00
GyDi
66abf27edd fix: import mod 2022-07-17 16:51:54 +08:00
GyDi
3d1b6d7de7 feat: theme mode support follows system 2022-07-17 16:02:17 +08:00
GyDi
115e604627 chore: update deps 2022-07-17 14:54:09 +08:00
GyDi
f9aeec8eb5 refactor: mode manage on tray 2022-07-13 02:26:54 +08:00
GyDi
acf4d15a8d chore: rm code 2022-07-13 01:07:29 +08:00
GyDi
e5e08d1762 fix: add tray separator 2022-07-13 01:05:53 +08:00
GyDi
178c7817fa Merge pull request #128 from Limsanity/main
feat: support switch proxy mode
2022-07-13 01:02:14 +08:00
limsanity
a93a428a89 style: resolve formatting problem 2022-07-13 00:54:47 +08:00
limsanity
d126a361e1 feat(system tray): support switch rule/global/direct/script mode in system tray 2022-07-13 00:43:27 +08:00
GyDi
9e42ec0c06 chore: update deps 2022-07-11 00:41:21 +08:00
GyDi
1f05654faa chore: update clash version 2022-07-08 01:42:14 +08:00
GyDi
3ddef6440a chore: update aarch rule 2022-07-07 02:54:20 +08:00
GyDi
036b47acbc chore: aarch upload script 2022-07-07 02:53:53 +08:00
GyDi
42e585e7a2 fix: instantiate core after init app, close #122 2022-07-06 15:14:33 +08:00
GyDi
93d066bef7 chore: update clash version 2022-07-06 00:54:01 +08:00
GyDi
580c6fca00 chore: update deps 2022-07-06 00:49:09 +08:00
GyDi
03300da93a fix: rm macOS transition props 2022-07-05 01:24:23 +08:00
GyDi
2a1fc38799 chore: update deps 2022-07-05 00:52:22 +08:00
GyDi
646afbfa36 chore: change tray icon 2022-06-22 01:26:25 +08:00
GyDi
45a1435ce7 v1.0.3 2022-06-20 02:09:12 +08:00
GyDi
f4d7d32fd7 chore: update log 2022-06-20 02:06:46 +08:00
GyDi
1b00f93091 chore: update clash version 2022-06-20 01:47:15 +08:00
GyDi
9c9e5a54a7 fix: improve external-controller parse and log 2022-06-20 01:36:56 +08:00
GyDi
4cf8c9c477 chore: update macos icon 2022-06-18 18:41:53 +08:00
GyDi
a78e1d7f2b fix: show windows on click 2022-06-18 17:28:37 +08:00
GyDi
00c1d6c42c chore: new logo for mac 2022-06-18 17:28:05 +08:00
GyDi
5d92f09449 chore: update alpha ci 2022-06-17 01:18:19 +08:00
GyDi
ac7d820e6f chore: update tauri 2022-06-17 01:16:46 +08:00
GyDi
7d752443ca fix: adjust update profile notice error 2022-06-15 02:42:04 +08:00
GyDi
3311b5a6e3 fix: style issue on mac 2022-06-15 02:41:37 +08:00
GyDi
3801443c9c chore: update version 2022-06-15 01:15:05 +08:00
GyDi
9b2a2eb229 feat: improve yaml file error log 2022-06-14 01:40:02 +08:00
GyDi
d165f66815 chore: update deps 2022-06-14 01:23:58 +08:00
GyDi
6715d52b81 Merge pull request #104 from FoundTheWOUT/main
fix: check script run on all OS
2022-06-12 22:25:05 +08:00
FoundTheWOUT
ae160556d0 fix: check script run on all OS 2022-06-05 23:55:41 +08:00
GyDi
c040e1f9b7 feat: save proxy page state 2022-06-04 18:55:39 +08:00
GyDi
db14a8ac1a chore: portable script 2022-06-02 00:56:05 +08:00
GyDi
9beafd03ee chore: updater script 2022-06-01 10:06:08 +08:00
GyDi
4f081c5e14 v1.0.2 2022-06-01 01:26:05 +08:00
GyDi
ff4e9e42e5 chore: update log 2022-06-01 01:25:39 +08:00
GyDi
d5e3e6f256 fix: macOS disable transparent 2022-06-01 01:04:46 +08:00
GyDi
cc9660c0e7 chore: rm some files 2022-06-01 00:49:52 +08:00
GyDi
90d92707c8 chore: rust cache 2022-06-01 00:47:04 +08:00
GyDi
fe77bc7c37 chore: adjust ci and script 2022-06-01 00:43:57 +08:00
GyDi
54a7d8291b chore: enable meta by default 2022-06-01 00:31:12 +08:00
GyDi
7bb924ce5b chore: rm file 2022-05-30 00:58:48 +08:00
GyDi
9f2e25309e chore: adjust code 2022-05-30 00:57:31 +08:00
GyDi
b32b39579e chore: update deps 2022-05-29 23:35:22 +08:00
GyDi
00513e66bc fix: window transparent and can not get hwnd 2022-05-25 19:06:06 +08:00
GyDi
8850867b82 fix: create main window 2022-05-25 16:45:18 +08:00
GyDi
d6dfbbf8dd chore: update deps 2022-05-25 16:12:48 +08:00
GyDi
6c4797bce4 chore: update clash core 2022-05-25 16:08:58 +08:00
ctaoist
2c97eb4115 feat: light mode wip (#96)
* 关闭窗口释放UI资源

* windows 还有左键点击事件

* 兼容enhance profile

* bug 修复
2022-05-25 16:06:39 +08:00
GyDi
54b0828c20 chore: update clash version 2022-05-18 14:14:37 +08:00
GyDi
0d241a4993 chore: alpha ci 2022-05-18 14:07:41 +08:00
GyDi
802b0d1a75 chore: error text 2022-05-18 09:58:20 +08:00
GyDi
e76a7716bc chore: ci 2022-05-17 12:35:42 +08:00
GyDi
a9cc0a61d9 chore: fix alpha ci 2022-05-17 12:34:30 +08:00
GyDi
fe50e47fc6 chore: alpha ci 2022-05-17 11:41:45 +08:00
GyDi
0006b286ca chore: meta 2022-05-17 02:46:15 +08:00
GyDi
17f99da45e chore: fix ci 2022-05-17 02:21:58 +08:00
GyDi
f7ce03a819 chore: test ci 2022-05-17 02:17:02 +08:00
GyDi
c073501ac0 chore: alpha 2022-05-17 02:11:29 +08:00
GyDi
d4a4747c08 feat: clash meta core supports 2022-05-17 01:59:49 +08:00
GyDi
e7b19a5f66 fix: adjust notice 2022-05-17 00:51:37 +08:00
GyDi
0070ec09a2 fix: label text 2022-05-16 20:26:13 +08:00
GyDi
b3ec62a3db feat: script mode 2022-05-16 20:18:56 +08:00
GyDi
34ca4ad3b0 chore: test ci 2022-05-16 17:55:14 +08:00
GyDi
e0909731a4 chore: update deps 2022-05-16 17:54:21 +08:00
GyDi
d8d1cb2eda feat: clash meta core support (wip) 2022-05-16 01:52:50 +08:00
GyDi
2a24fbadae fix: icon path 2022-05-13 12:28:36 +08:00
GyDi
85e1c4bd47 fix: icon issue 2022-05-13 02:46:06 +08:00
GyDi
09fcb0739d fix: notice ui blocking 2022-05-13 02:29:43 +08:00
GyDi
0882c2b5c7 fix: service mode error 2022-05-13 02:11:03 +08:00
GyDi
bb7763d72b chore: fix linux build ci 2022-05-11 12:39:41 +08:00
GyDi
b919d7364a chore: test ci 2022-05-11 12:20:20 +08:00
GyDi
942a37290c chore: test ci 2022-05-11 12:12:55 +08:00
GyDi
ef24c6cbbd chore: test ci fix deps 2022-05-11 11:30:19 +08:00
GyDi
cf05571f1c chore: test ci 2022-05-11 11:22:09 +08:00
GyDi
0fa038b4f2 chore: test ci 2022-05-11 10:51:28 +08:00
GyDi
10a33a2978 chore: continue on error 2022-05-11 01:26:11 +08:00
GyDi
60a88381cf chore: test linux 2022-05-11 00:52:20 +08:00
GyDi
48d83c7d44 chore: test ci 2022-05-11 00:31:19 +08:00
GyDi
b967b7ed6b v1.0.1 2022-05-10 21:40:33 +08:00
GyDi
b60ecb6171 chore: update log 2022-05-10 21:40:07 +08:00
GyDi
e8b80216e8 fix: win11 drag lag 2022-05-09 14:41:26 +08:00
GyDi
69a5472a16 chore: change default height 2022-05-09 14:01:46 +08:00
GyDi
bd6eb606ca fix: rm unwrap 2022-05-09 14:01:14 +08:00
GyDi
e7f1e83a43 feat: reduce gpu usage when hidden 2022-05-07 14:43:52 +08:00
GyDi
357fe3b793 feat: interval update from now field 2022-05-07 14:43:08 +08:00
GyDi
ad6c06409e feat: adjust theme 2022-05-06 14:04:39 +08:00
GyDi
2a7feba808 fix: edit profile info 2022-05-06 12:46:27 +08:00
GyDi
45b7e28db2 feat: supports more remote headers close #81 2022-05-06 10:52:59 +08:00
GyDi
daa9479352 feat: check the remote profile 2022-05-06 01:26:24 +08:00
GyDi
0f688bd71a fix: change window default size 2022-05-06 01:15:15 +08:00
GyDi
ffe0843bd6 chore: update deps 2022-05-06 01:14:13 +08:00
GyDi
0ee4039853 chore: change default user agent 2022-05-06 00:42:20 +08:00
GyDi
a3a6e8cdf6 chore: merge
feat: remove outdated config
2022-04-28 16:28:18 +08:00
tianyoulan
30f60f87f4 feat: fix typo 2022-04-28 15:35:17 +08:00
tianyoulan
0d91657557 feat: remove trailing comma 2022-04-28 15:05:10 +08:00
tianyoulan
ec9b80a64e feat: remove outdated config 2022-04-28 15:02:37 +08:00
GyDi
91909f8886 fix: change service installer and uninstaller 2022-04-27 15:46:44 +08:00
GyDi
cc335dae38 v1.0.0 2022-04-26 00:50:48 +08:00
GyDi
0d6ea03ac8 chore: update log 2022-04-26 00:49:07 +08:00
GyDi
e1370b75e8 fix: adjust connection scroll 2022-04-26 00:37:28 +08:00
GyDi
c2d0ccea3c fix: adjust something 2022-04-25 20:00:11 +08:00
GyDi
9319851118 fix: adjust debounce wait time 2022-04-25 19:39:21 +08:00
GyDi
d45ad76fbc feat: windows service mode ui 2022-04-25 16:12:04 +08:00
GyDi
d8b9c3b832 feat: add some commands 2022-04-24 21:03:47 +08:00
GyDi
9f2534af3d feat: windows service mode 2022-04-24 21:00:17 +08:00
GyDi
2c2978b93a chore: update check script 2022-04-24 15:35:30 +08:00
GyDi
c9b0f22a2e wip: windows service mode 2022-04-23 17:26:32 +08:00
GyDi
2959cd140b test: windows service 2022-04-21 21:28:44 +08:00
GyDi
7834cfe5f0 chore: adjust guard log 2022-04-21 19:51:20 +08:00
GyDi
2b9c5d0cff fix: adjust dns config 2022-04-21 19:50:22 +08:00
GyDi
cb3b51dcde feat: add update interval 2022-04-21 14:26:41 +08:00
GyDi
0c23fe96be chore: adjust 2022-04-21 14:24:35 +08:00
GyDi
9d0bd73ee6 chore: update clash version 2022-04-20 21:02:41 +08:00
GyDi
15f096f764 feat: refactor and supports cron tasks 2022-04-20 20:39:27 +08:00
GyDi
9dd41be608 feat: supports cron update profiles 2022-04-20 20:37:16 +08:00
GyDi
17f8703c71 refactor: verge 2022-04-20 11:17:54 +08:00
GyDi
5edfd7b6f7 refactor: wip 2022-04-20 01:44:47 +08:00
GyDi
4371772ec7 refactor: mutex 2022-04-19 14:38:59 +08:00
GyDi
8e4e08a828 fix: traffic graph adapt to different fps 2022-04-19 13:55:26 +08:00
GyDi
401e6b5a57 refactor: wip 2022-04-19 01:41:20 +08:00
GyDi
01dfe83fcd chore: check script proxy agent supports 2022-04-17 00:37:21 +08:00
GyDi
177c5700ff feat: optimize traffic graph quadratic curve 2022-04-16 22:32:44 +08:00
GyDi
5d401a4fbb feat: optimize the animation of the traffic graph 2022-04-16 17:28:30 +08:00
GyDi
c60ab97103 fix: optimize clash launch 2022-04-15 01:29:25 +08:00
GyDi
f3d7a90ac7 fix: reset after exit 2022-04-14 01:29:33 +08:00
GyDi
7b114f961a fix: adjust code 2022-04-14 01:29:02 +08:00
GyDi
8caaf13c84 v0.0.29 2022-04-13 01:30:08 +08:00
GyDi
0218da1ee6 chore: update log 2022-04-13 01:29:48 +08:00
GyDi
f4786755f1 chore: update dep 2022-04-13 01:21:49 +08:00
GyDi
b9b1fcb4e5 chore: rename green to portable 2022-04-13 01:17:40 +08:00
GyDi
5bfda557bc feat: system tray add tun mode 2022-04-13 01:09:51 +08:00
GyDi
6e53df403e fix: adjust log 2022-04-13 00:49:30 +08:00
GyDi
4a7246ec6e feat: supports change config dir 2022-04-12 23:09:32 +08:00
GyDi
f53a021c08 chore: update clash version 2022-04-12 23:04:19 +08:00
GyDi
8fa95d06e1 feat: add default user agent 2022-04-12 01:05:51 +08:00
GyDi
b34bd9ffd2 chore: rm console 2022-04-11 02:26:09 +08:00
GyDi
fd1a7e34a8 feat: connections page supports filter 2022-04-11 02:25:34 +08:00
GyDi
973653a690 feat: log page supports filter 2022-04-11 01:46:33 +08:00
GyDi
11e4dfe940 fix: check button hover style 2022-04-10 03:33:17 +08:00
GyDi
e7804d39b3 feat: optimize delay checker concurrency strategy 2022-04-10 02:58:48 +08:00
GyDi
6cfbde43ab feat: support sort proxy node and custom test url 2022-04-10 02:09:36 +08:00
GyDi
30421bf247 chore: update deps 2022-04-10 01:31:05 +08:00
GyDi
ab1b6b45a2 refactor: proxy head 2022-04-08 01:35:18 +08:00
GyDi
59366c04dd v0.0.28 2022-04-07 01:28:30 +08:00
GyDi
fac437c1ef chore: update log 2022-04-07 01:27:48 +08:00
GyDi
797f0d92b3 feat: handle remote clash config fields 2022-04-07 01:20:44 +08:00
GyDi
75f3fa5144 fix: icon button color inherit 2022-04-06 22:52:00 +08:00
GyDi
6ea1b5c7c3 fix: remove the lonely zero 2022-04-06 22:48:10 +08:00
GyDi
6a81cc0e83 chore: readme 2022-04-06 01:52:20 +08:00
GyDi
d3e2c7c51a v0.0.27 2022-04-06 01:21:33 +08:00
GyDi
1c930a2eb9 chore: update log 2022-04-06 01:19:13 +08:00
GyDi
fb1ea5b37c refactor: update profile menu 2022-04-06 01:13:06 +08:00
GyDi
e82f344a2c chore: add updater script 2022-04-05 23:24:52 +08:00
GyDi
10ba205f97 chore: updater json supports arch field 2022-04-05 23:19:54 +08:00
GyDi
fbc8b97c25 chore: add ci 2022-04-05 20:21:53 +08:00
GyDi
c6a31b21e3 refactor: enhanced mode ui component 2022-04-03 01:41:48 +08:00
GyDi
4aa8c4b79d feat: add text color 2022-04-02 17:18:38 +08:00
GyDi
04179ae833 fix: i18n add value 2022-04-02 13:49:48 +08:00
GyDi
205ecde505 fix: proxy page first render 2022-04-02 01:23:31 +08:00
GyDi
7cb467c960 feat: control final tun config 2022-04-01 23:55:44 +08:00
GyDi
cbe7c69154 chore: add debug feature 2022-04-01 23:43:35 +08:00
GyDi
c93cbb614d feat: support css injection 2022-04-01 02:08:42 +08:00
GyDi
42c570a6e2 fix: console warning 2022-04-01 01:16:23 +08:00
GyDi
aed47c1178 feat: support theme setting 2022-03-31 23:38:00 +08:00
GyDi
a1ac4784c8 feat: add text color 2022-03-31 23:34:36 +08:00
GyDi
d8b751d56b feat: add theme setting 2022-03-31 23:11:50 +08:00
GyDi
213eb2ae88 refactor: ui theme 2022-03-30 12:36:39 +08:00
GyDi
17b7265a9a chore: add api 2022-03-30 01:30:22 +08:00
GyDi
3afdf9571e fix: icon button title 2022-03-30 01:29:25 +08:00
GyDi
bb8a59d1db chore: update tauri 2022-03-29 23:05:32 +08:00
GyDi
d26830d81e fix: macOS transition flickers close #47 2022-03-29 01:39:54 +08:00
GyDi
1f2c703ddc chore: update deps 2022-03-29 01:33:14 +08:00
GyDi
dd51a6e4d1 fix: csp image data 2022-03-28 23:17:11 +08:00
GyDi
caa7597097 chore: readme 2022-03-28 01:33:58 +08:00
GyDi
409996cb29 v0.0.26 2022-03-28 01:30:54 +08:00
GyDi
a15b37d177 chore: update log 2022-03-28 01:18:18 +08:00
GyDi
a436ef2126 fix: close dialog after save 2022-03-28 01:09:34 +08:00
GyDi
4c53e6e89d refactor: optimize enhance mode strategy 2022-03-28 01:07:14 +08:00
GyDi
083eb98949 fix: change to deep copy 2022-03-28 00:56:48 +08:00
GyDi
418951415c feat: enhanced mode supports more fields 2022-03-27 20:36:13 +08:00
GyDi
bac0d0a175 chore: profile release 2022-03-27 01:38:40 +08:00
GyDi
7bde7ebc30 feat: supports edit profile file 2022-03-27 00:58:17 +08:00
GyDi
7b7e6555c1 feat: supports silent start 2022-03-26 18:56:16 +08:00
GyDi
ea8565d0ca feat: use crate open 2022-03-23 23:00:14 +08:00
GyDi
1ba2902618 fix: window style close #45 2022-03-23 21:43:13 +08:00
GyDi
e91e8d565a feat: enhance connections display order 2022-03-23 14:02:08 +08:00
GyDi
206e613ace chore: adjust style 2022-03-23 13:58:57 +08:00
GyDi
9a88dcf33e fix: manage global proxy correctly 2022-03-23 12:49:34 +08:00
GyDi
9fc9e8972f fix: tauri csp 2022-03-23 01:47:35 +08:00
GyDi
4fe6703dd4 feat: save global selected 2022-03-22 12:38:59 +08:00
GyDi
d613cbb68f v0.0.25 2022-03-22 01:51:06 +08:00
GyDi
be2247b36f chore: update log 2022-03-22 01:50:24 +08:00
GyDi
66d1f49116 chore: update clash core 2022-03-22 01:50:15 +08:00
GyDi
37d35bbbdb fix: windows style 2022-03-22 01:36:06 +08:00
GyDi
321d4ccc8e fix: update state 2022-03-22 01:27:22 +08:00
inRm3D
6e4338d13c chore: readme (#43) 2022-03-21 21:55:45 +08:00
GyDi
677d15d8a7 chore: tauri feature 2022-03-21 11:40:52 +08:00
GyDi
a7e978dc06 fix: profile item loading state 2022-03-21 11:40:27 +08:00
GyDi
67f5218a73 chore: tauri allowList 2022-03-20 13:16:50 +08:00
GyDi
c2d1329b8b chore: update clash version 2022-03-20 12:37:55 +08:00
GyDi
0530ec0651 v0.0.24 2022-03-20 01:50:39 +08:00
GyDi
8a6e18badc chore: update log 2022-03-20 01:50:15 +08:00
GyDi
1a144a7070 feat: system tray supports system proxy setting 2022-03-20 01:36:43 +08:00
GyDi
e115432e11 feat: prevent context menu on Windows close #22 2022-03-19 19:28:53 +08:00
GyDi
145f6e2d53 feat: create local profile with selected file 2022-03-19 19:21:55 +08:00
GyDi
9c62311b56 feat: reduce the impact of the enhanced mode 2022-03-19 16:02:47 +08:00
GyDi
a150d91c48 fix: adjust windows style 2022-03-19 15:35:59 +08:00
GyDi
4730a4e352 feat: parse update log 2022-03-19 15:31:58 +08:00
GyDi
67c57be830 chore: update log supports 2022-03-19 14:04:58 +08:00
GyDi
d98e72ca25 chore: updater proxy 2022-03-19 11:14:12 +08:00
GyDi
d81fa1e610 chore: ci support linux 2022-03-19 10:50:44 +08:00
GyDi
8c12de8b73 chore: readme 2022-03-18 19:02:53 +08:00
GyDi
9b5218f85b feat: fill i18n 2022-03-18 14:59:23 +08:00
GyDi
a6cf5c7667 feat: dayjs i18n 2022-03-18 14:45:24 +08:00
GyDi
4fca73ffbd feat: connections page simply support 2022-03-18 14:43:22 +08:00
GyDi
46a9023782 feat: add wintun.dll by default 2022-03-18 11:49:00 +08:00
GyDi
5fed443476 fix: change mixed port error 2022-03-18 11:39:02 +08:00
GyDi
0fab0f207c chore: add dev feature 2022-03-18 10:59:35 +08:00
GyDi
ee67358ee6 chore: rename temp dir 2022-03-18 10:20:24 +08:00
GyDi
cefd44304b chore: resolve wintun.dll 2022-03-17 19:29:30 +08:00
GyDi
2a88cdc76d fix: auto launch path 2022-03-16 18:44:25 +08:00
GyDi
4c39d37ad5 fix: tun mode config 2022-03-15 14:23:57 +08:00
GyDi
9b9865f717 fix: adjsut open cmd error 2022-03-15 12:51:39 +08:00
GyDi
3f0456322d chore: fix ci env 2022-03-15 00:00:20 +08:00
GyDi
16ac005fd6 v0.0.23 2022-03-14 01:34:26 +08:00
GyDi
003fcf446a feat: event emit when clash config update 2022-03-13 01:37:41 +08:00
GyDi
39703120a4 chore: fix ci 2022-03-13 00:59:42 +08:00
GyDi
656c0efc0d chore: update tauri action 2022-03-13 00:58:49 +08:00
GyDi
63f7c0ed69 chore: green version bundle 2022-03-13 00:52:31 +08:00
GyDi
5103463524 chore: rm then 2022-03-12 23:58:20 +08:00
GyDi
97254a1e3a feat: i18n supports 2022-03-12 23:07:45 +08:00
GyDi
9f171a01e8 fix: parse external-controller 2022-03-12 21:35:20 +08:00
GyDi
6c9b6007a3 fix: config file case close #18 2022-03-12 21:03:17 +08:00
GyDi
f4a7c4bd69 chore: merge 2022-03-12 18:45:07 +08:00
ttyS3
5abad18e51 chore: show open app and log dir failed message 2022-03-12 18:04:56 +08:00
GyDi
57f70a6d35 chore: adjust error log 2022-03-12 15:57:16 +08:00
GyDi
cb78f3a707 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
b331db74ee feat: add Linux open dir support
refactor(logging): refine open dir failed log message
2022-03-12 03:18:25 +08:00
GyDi
8fb9ad3fe7 fix: patch item option 2022-03-11 19:56:56 +08:00
GyDi
d4b5c55169 fix: user agent not works 2022-03-11 19:50:51 +08:00
GyDi
a41438d779 v0.0.22 2022-03-10 02:27:00 +08:00
GyDi
0895d8a813 chore: rm dead code 2022-03-10 02:25:35 +08:00
GyDi
89310d7b7c fix: external-controller 2022-03-10 02:19:06 +08:00
GyDi
ca0e936f7c feat: support more options for remote profile 2022-03-10 02:03:55 +08:00
GyDi
afe767e28a chore: reduce icon size on macOS 2022-03-10 01:04:53 +08:00
GyDi
1481b011d9 chore: adjust icon 2022-03-10 00:04:59 +08:00
GyDi
5f1b968b60 chore: add linux link 2022-03-09 20:14:15 +08:00
GyDi
5a729043ce feat: linux system proxy 2022-03-09 19:48:32 +08:00
GyDi
2de0dfcef7 chore: update deps 2022-03-09 02:36:40 +08:00
GyDi
7cc83ed080 fix: change proxy bypass on mac 2022-03-09 02:36:03 +08:00
GyDi
31ec03f8e5 fix: kill sidecars after install still in test 2022-03-09 02:09:51 +08:00
GyDi
0d095c4cf1 fix: log some error 2022-03-09 02:00:56 +08:00
GyDi
dbde09d008 chore: rename productName 2022-03-09 01:53:34 +08:00
GyDi
43d33e21e6 chore: update tauri version 2022-03-09 01:50:37 +08:00
GyDi
c8cf43a255 fix: apply_blur parameter 2022-03-08 10:44:41 +08:00
GyDi
b2d05672b1 fix: limit enhanced profile range 2022-03-08 01:45:46 +08:00
GyDi
4123c789e6 chore: update deps 2022-03-08 01:18:20 +08:00
GyDi
c65f3c7b68 chore: add default bypass 2022-03-08 00:39:09 +08:00
GyDi
41e3642e02 fix: profile updated field 2022-03-07 01:41:42 +08:00
GyDi
eae7602f80 fix: profile field check 2022-03-07 01:30:32 +08:00
GyDi
fd12c73cf9 fix: create dir panic 2022-03-07 01:06:21 +08:00
GyDi
2ef9d70224 v0.0.21 2022-03-06 18:36:02 +08:00
GyDi
3861531fb7 chore: default release body 2022-03-06 18:34:38 +08:00
GyDi
aeff41ff41 chore: update deps 2022-03-06 18:04:44 +08:00
GyDi
b5c8283575 fix: only error when selected 2022-03-06 17:06:45 +08:00
GyDi
b31cdce073 feat: enhance profile status 2022-03-06 17:02:29 +08:00
GyDi
6040aae12a feat: menu item refresh enhanced mode 2022-03-06 15:46:16 +08:00
GyDi
6ba4afc5bb fix: enhanced profile consistency 2022-03-06 15:40:16 +08:00
GyDi
4707e2b02a feat: profile enhanced mode 2022-03-06 14:59:25 +08:00
GyDi
08dd73fd72 feat: profile enhanced ui 2022-03-05 22:54:39 +08:00
GyDi
83bf67b8ca feat: profile item adjust 2022-03-05 19:04:20 +08:00
GyDi
b758ead371 fix: simply compatible with proxy providers 2022-03-05 16:18:44 +08:00
GyDi
357a0db03e fix: component warning 2022-03-05 15:48:39 +08:00
GyDi
6a3219a5e8 chore: update copyright 2022-03-05 01:51:29 +08:00
GyDi
b7e7e82f85 fix: when updater failed 2022-03-04 02:10:27 +08:00
GyDi
e4c66c8b2b fix: log file 2022-03-04 02:08:26 +08:00
GyDi
4d4f8b5ee8 chore: update deps 2022-03-03 20:37:06 +08:00
GyDi
5ab75f7eb9 chore: enhance wip 2022-03-03 01:58:05 +08:00
GyDi
8ff22bc737 fix: result 2022-03-03 01:56:47 +08:00
GyDi
1d65287fbb feat: enhanced profile (wip) 2022-03-03 01:52:02 +08:00
GyDi
dc24993bf6 v0.0.20 2022-03-02 01:59:53 +08:00
GyDi
ef606d1365 feat: edit profile item 2022-03-02 01:58:16 +08:00
GyDi
e26ecec545 fix: cover profile extra 2022-03-02 01:45:00 +08:00
GyDi
28e8aa9111 chore: adjust log field 2022-03-02 01:13:31 +08:00
GyDi
a8ff67e3b8 chore: rm file 2022-03-02 00:58:07 +08:00
GyDi
c311b92ae2 feat: use nanoid 2022-03-02 00:56:05 +08:00
GyDi
b04e22cd2c feat: compatible profile config 2022-03-01 11:05:33 +08:00
GyDi
4903c70686 refactor: profile config 2022-03-01 08:58:47 +08:00
GyDi
04aa963b9a v0.0.19 2022-02-28 01:59:53 +08:00
GyDi
7fdd2e81cf wip: refactor profile 2022-02-28 01:59:13 +08:00
GyDi
38778c8cac refactor: use anyhow to handle error 2022-02-28 01:34:25 +08:00
GyDi
565851b113 fix: display menu only on macos 2022-02-27 01:47:56 +08:00
GyDi
cd216b0591 fix: proxy global showType 2022-02-27 01:33:22 +08:00
GyDi
031e2acc63 feat: native menu supports 2022-02-27 01:29:57 +08:00
GyDi
b3afa6b0a4 chore: update deps 2022-02-27 01:16:43 +08:00
GyDi
40fc2b78d3 feat: filter proxy and display type 2022-02-27 00:58:14 +08:00
GyDi
24e6c1ea36 feat: use lock fn 2022-02-26 17:39:36 +08:00
GyDi
275b4e2944 feat: refactor proxy page 2022-02-26 17:39:08 +08:00
GyDi
061c93cb7d feat: proxy group auto scroll to current 2022-02-26 01:21:39 +08:00
GyDi
3b91117724 v0.0.18 2022-02-25 02:13:41 +08:00
GyDi
eb4fac28c4 chore: ci 2022-02-25 02:12:55 +08:00
GyDi
acb5fc6393 feat: clash tun mode supports 2022-02-25 02:09:39 +08:00
GyDi
961ed728a1 feat: use enhanced guard-state 2022-02-25 01:22:33 +08:00
GyDi
678fcfc3a3 feat: guard state supports debounce guard 2022-02-25 01:21:13 +08:00
GyDi
79d60e6b4a feat: adjust clash version display 2022-02-24 23:04:18 +08:00
GyDi
858b2ff6da feat: hide command window 2022-02-24 22:46:12 +08:00
GyDi
ba3355c74c fix: use full clash config 2022-02-23 23:23:07 +08:00
GyDi
6e3028cf86 feat: enhance log data 2022-02-23 02:00:45 +08:00
GyDi
a28598630d chore: update readme 2022-02-22 21:54:33 +08:00
GyDi
36810b2d42 v0.0.17 2022-02-22 02:08:31 +08:00
GyDi
de9923f2e1 fix: reconnect websocket when restart clash 2022-02-22 02:05:22 +08:00
GyDi
716b1ac528 chore: enhance publish ci 2022-02-22 01:56:06 +08:00
GyDi
77663d64a0 fix: wrong exe path 2022-02-21 22:33:37 +08:00
GyDi
49540c7151 chore: use tauri cli 2022-02-21 22:31:00 +08:00
GyDi
03710bc6ba v0.0.16 2022-02-21 01:33:11 +08:00
GyDi
489ae6c9b6 feat: change window style 2022-02-20 23:46:13 +08:00
GyDi
3b54eeb1c5 feat: fill verge template 2022-02-20 20:12:55 +08:00
GyDi
a9bd753e38 feat: enable customize guard duration 2022-02-20 20:11:39 +08:00
GyDi
bf15580081 feat: system proxy guard 2022-02-20 18:53:21 +08:00
GyDi
5b5a299b55 feat: enable show or hide traffic graph 2022-02-19 18:14:40 +08:00
GyDi
051b529c11 fix: patch verge config 2022-02-19 17:32:23 +08:00
GyDi
fa5fb815fb feat: traffic line graph 2022-02-19 17:02:24 +08:00
GyDi
a4c0a61698 chore: update deps 2022-02-19 00:34:09 +08:00
GyDi
80d8a8190d feat: adjust profile item ui 2022-02-19 00:23:47 +08:00
GyDi
e69a62c41a feat: adjust fetch profile url 2022-02-19 00:09:36 +08:00
GyDi
46183d4d43 feat: inline config file template 2022-02-18 23:57:13 +08:00
GyDi
4f2d3cc250 chore: update check script 2022-02-18 23:49:39 +08:00
GyDi
79daf543b0 fix: fetch profile panic 2022-02-17 13:44:26 +08:00
GyDi
5b83fb496c feat: kill sidecars when update app 2022-02-17 02:10:25 +08:00
GyDi
3dfe7c27cd feat: delete file 2022-02-17 01:58:12 +08:00
GyDi
e50538c634 feat: lock some async functions 2022-02-17 01:42:25 +08:00
GyDi
92b6ecc4dd chore: tauri updater env 2022-02-16 11:02:00 +08:00
GyDi
588ade0ab2 chore: add pubkey 2022-02-16 10:59:31 +08:00
GyDi
4375aefb49 v0.0.15 2022-02-16 03:22:25 +08:00
GyDi
c5cba7775a feat: support open dir 2022-02-16 03:21:34 +08:00
GyDi
7cf8ec6d61 fix: spawn command 2022-02-16 02:43:52 +08:00
GyDi
46cbd3ae7b feat: change allow list 2022-02-16 02:42:56 +08:00
GyDi
678cf8a341 feat: support check delay 2022-02-16 02:22:01 +08:00
GyDi
3a0f1f6197 fix: import error 2022-02-15 01:36:19 +08:00
GyDi
902ff23a95 feat: scroll to proxy item 2022-02-15 01:34:10 +08:00
GyDi
7e2e9aa836 chore: update deps 2022-02-15 00:33:58 +08:00
GyDi
491ef4559f fix: not open file when new profile 2022-02-15 00:21:34 +08:00
GyDi
b7bad0f585 feat: edit system proxy bypass 2022-02-14 01:26:24 +08:00
GyDi
ce248bc169 feat: disable user select 2022-02-13 19:52:35 +08:00
GyDi
d599be7e7c fix: reset value correctly 2022-02-13 19:40:31 +08:00
GyDi
5b1fc0e6c0 feat: new profile able to edit name and desc 2022-02-13 19:33:24 +08:00
GyDi
aeece6c201 chore: adjust files 2022-02-13 19:27:24 +08:00
GyDi
d7a1b974cd feat: update tauri version 2022-02-13 18:45:03 +08:00
GyDi
0db1872bd2 fix: something 2022-02-12 21:12:39 +08:00
GyDi
4333bc7004 fix: menu without fragment 2022-02-10 01:42:52 +08:00
GyDi
f6caac749e feat: display clash core version 2022-02-10 01:35:55 +08:00
GyDi
723303e1b6 feat: adjust profile item menu 2022-02-10 01:14:57 +08:00
GyDi
0daf5bae96 v0.0.14 2022-02-09 02:11:01 +08:00
GyDi
6d59fe2a34 feat: profile item ui 2022-02-09 02:08:27 +08:00
GyDi
3ed0f005ee fix: proxy list error 2022-02-09 02:02:29 +08:00
GyDi
66b89ee817 feat: support new profile 2022-02-07 17:26:05 +08:00
GyDi
c596c875a6 feat: support open command for viewing 2022-02-07 16:45:20 +08:00
GyDi
08c1a29383 chore: add item template yaml 2022-02-02 20:56:06 +08:00
GyDi
ab078d1ea0 fix: something 2022-01-31 23:34:58 +08:00
GyDi
b028b09041 fix: macos auto launch fail 2022-01-31 23:32:41 +08:00
GyDi
44c3eb7b35 chore: update deps and app name 2022-01-31 23:27:11 +08:00
GyDi
f54052c320 chore: cargo update 2022-01-28 22:02:17 +08:00
GyDi
d23a055e3b chore: update clash version 2022-01-28 22:00:15 +08:00
GyDi
8f76f3184f v0.0.13 2022-01-25 02:11:00 +08:00
GyDi
619c4853fd feat: global proxies use virtual list 2022-01-25 02:08:10 +08:00
GyDi
58fb6ade7c feat: enable change proxy mode 2022-01-25 01:51:44 +08:00
GyDi
f2c0626c68 feat: update styles 2022-01-25 01:48:26 +08:00
GyDi
3956d2bd63 feat: manage clash mode 2022-01-24 23:13:13 +08:00
GyDi
1d851631d6 chore: update readme 2022-01-22 23:57:39 +08:00
GyDi
517e38e33f v0.0.12 2022-01-21 03:09:21 +08:00
GyDi
a2644a4ebf chore: update ci 2022-01-21 03:08:40 +08:00
GyDi
f7bf6a8080 fix: type error 2022-01-21 03:08:20 +08:00
GyDi
681c1d9e9a v0.0.12 2022-01-21 02:59:33 +08:00
GyDi
9ab2e69f4f refactor: rename profiles & command state 2022-01-21 02:57:15 +08:00
GyDi
9af2ab57d1 feat: change system porxy when changed port 2022-01-21 02:50:13 +08:00
GyDi
f14e4dc0be feat: enable change mixed port 2022-01-21 02:32:23 +08:00
GyDi
39bd9641f4 feat: manage clash config 2022-01-21 02:31:44 +08:00
GyDi
177c283011 chore: add ahooks 2022-01-21 02:29:45 +08:00
GyDi
62ff1421e7 fix: restart clash should update something 2022-01-21 00:29:33 +08:00
GyDi
2577cbe1ac feat: enable update clash info 2022-01-20 23:41:08 +08:00
GyDi
7542f065a1 feat: rename edit as view 2022-01-19 23:58:34 +08:00
GyDi
aa2abb92ef chore: adjust ci 2022-01-19 23:55:05 +08:00
GyDi
efc00c1610 fix: script error... 2022-01-19 00:49:23 +08:00
GyDi
7585893cf0 fix: tag error 2022-01-19 00:46:43 +08:00
GyDi
4fd9019c25 fix: script error 2022-01-19 00:42:37 +08:00
GyDi
f4990aaa95 feat: test auto gen update.json ci 2022-01-19 00:37:59 +08:00
GyDi
834fa77385 v0.0.11 2022-01-17 02:53:07 +08:00
GyDi
3acd20c04d fix: remove cargo test 2022-01-17 02:50:19 +08:00
GyDi
878859d7cc v0.0.11 2022-01-17 02:44:08 +08:00
GyDi
7038776ec5 feat: adjust setting typography 2022-01-17 02:42:52 +08:00
GyDi
a74001ef6a feat: enable force select profile 2022-01-17 02:28:23 +08:00
GyDi
0bad1794c4 feat: support edit profile item 2022-01-17 02:16:17 +08:00
GyDi
ceec72b869 fix: reduce proxy item height 2022-01-17 02:15:54 +08:00
GyDi
5c1ed0ec06 feat: adjust control ui 2022-01-17 02:15:06 +08:00
GyDi
ce00fc502b feat: update profile supports noproxy 2022-01-16 22:57:42 +08:00
GyDi
ab82d2af4f refactor: something 2022-01-16 18:30:25 +08:00
GyDi
2eb3e1aef6 fix: put profile request with no proxy 2022-01-16 18:20:01 +08:00
GyDi
32cb0388cf refactor: notice caller 2022-01-16 17:56:43 +08:00
GyDi
30f9039a2c chore: change ci 2022-01-16 16:03:53 +08:00
GyDi
8bad63f72d fix: ci strategy 2022-01-16 14:30:49 +08:00
GyDi
aec4d89550 chore: enhance ci 2022-01-16 14:27:41 +08:00
GyDi
e8639a37af feat: rename page 2022-01-16 03:25:50 +08:00
GyDi
a92ab2cbe3 refactor: setting page 2022-01-16 03:22:37 +08:00
GyDi
fb0bb6e7f3 feat: refactor and adjust ui 2022-01-16 03:11:07 +08:00
GyDi
8dbf870122 feat: rm some commands 2022-01-16 03:09:36 +08:00
GyDi
50dab48f25 feat: change type 2022-01-15 21:58:13 +08:00
GyDi
e0c6354ff3 feat: supports auto launch on macos and windows 2022-01-15 21:55:05 +08:00
GyDi
5b03d251fd chore: add auto-launch 2022-01-15 21:00:19 +08:00
GyDi
9c4b69d1ae v0.0.10 2022-01-13 02:53:29 +08:00
GyDi
feba220fff feat: adjust proxy page 2022-01-13 02:51:30 +08:00
GyDi
f9efa3bf8f feat: press esc hide the window 2022-01-13 02:11:50 +08:00
GyDi
6ebf333982 fix: version update error 2022-01-12 22:19:44 +08:00
GyDi
2c7d07d606 v0.0.9 2022-01-12 02:55:07 +08:00
GyDi
b05185e7cc feat: show system proxy info 2022-01-12 02:54:50 +08:00
GyDi
148547de95 feat: support blur window 2022-01-12 02:27:29 +08:00
GyDi
fb6a97789e chore: add macos startup todo 2022-01-11 23:14:43 +08:00
GyDi
271f34c7e3 chore: cargo update 2022-01-11 23:13:02 +08:00
GyDi
9309f877d1 v0.0.8 2022-01-11 02:27:04 +08:00
GyDi
88db5fb38e chore: update ci 2022-01-11 02:26:50 +08:00
GyDi
4ee0a1b8c5 feat: windows support startup 2022-01-11 02:24:43 +08:00
GyDi
7617d734e2 feat: window self startup 2022-01-11 02:21:51 +08:00
GyDi
a15ae9badb fix: text 2022-01-10 22:57:21 +08:00
GyDi
97b6fed909 chore: ignore update json 2022-01-10 22:08:57 +08:00
GyDi
ab653afae9 fix: update profile after restart clash 2022-01-10 22:08:18 +08:00
GyDi
df158a741a fix: get proxies multiple times 2022-01-10 21:25:41 +08:00
GyDi
fb1371ebfa docs: fix img width 2022-01-10 02:52:12 +08:00
GyDi
82d5494235 docs: update 2022-01-10 02:49:42 +08:00
GyDi
6b754723d9 v0.0.7 2022-01-10 02:17:35 +08:00
GyDi
e003113132 feat: use tauri updater 2022-01-10 02:15:38 +08:00
GyDi
dda492ff87 feat: support update checker 2022-01-10 02:05:35 +08:00
GyDi
2459699d27 v0.0.6 2022-01-09 21:36:43 +08:00
GyDi
989acec027 chore: ci add macos 2022-01-09 21:34:14 +08:00
GyDi
701ab8b24f feat: support macos proxy config 2022-01-09 21:19:35 +08:00
GyDi
ed6bfa5fa2 feat: custom window decorations 2022-01-08 22:23:48 +08:00
GyDi
2d2899b68a feat: profiles add menu and delete button 2022-01-08 16:52:18 +08:00
GyDi
2d8b80bfa9 fix: delete profile item command 2022-01-08 14:21:12 +08:00
GyDi
c1cc560458 chore: temp 2022-01-08 02:54:37 +08:00
GyDi
95fb562533 v0.0.5 2022-01-08 02:12:56 +08:00
GyDi
17853ac237 chore: enhance ci 2022-01-08 02:12:12 +08:00
GyDi
e064a8bbdb chore: rename script 2022-01-08 01:56:28 +08:00
GyDi
8b9e3f1b59 chore: update publish script 2022-01-08 01:51:24 +08:00
GyDi
1d38739016 refactor: rename 2022-01-08 01:27:25 +08:00
GyDi
3bb46eaa6f refactor: impl structs methods 2022-01-07 23:29:20 +08:00
GyDi
3b1561a99b chore: update clash version 2022-01-06 23:40:57 +08:00
GyDi
a61e5c7310 fix: initialize profiles state 2022-01-05 23:30:18 +08:00
GyDi
ef8d1ebc0e chore: fixed tauri rev 2022-01-05 23:27:26 +08:00
GyDi
348b3d3738 feat: delay put profiles and retry 2022-01-05 02:01:32 +08:00
GyDi
3550aa10ba refactor: impl as struct methods 2022-01-05 02:00:59 +08:00
GyDi
c29eadd9ee feat: Window Send and Sync 2022-01-04 21:29:04 +08:00
GyDi
aecefaee8d chore: update tauri 2022-01-04 21:25:00 +08:00
GyDi
3bab5ec3a7 feat: support restart sidecar tray event 2021-12-31 18:24:50 +08:00
GyDi
3c908963ae feat: prevent click same 2021-12-31 18:21:35 +08:00
GyDi
cba176cdc9 fix: item header bgcolor 2021-12-31 18:19:53 +08:00
GyDi
cfdc854409 feat: scroller stable 2021-12-31 18:19:17 +08:00
GyDi
1d87f78088 feat: compatible with macos(wip) 2021-12-29 18:49:38 +08:00
GyDi
63b01376d6 chore: change tauri to git repo 2021-12-29 18:47:29 +08:00
GyDi
010e518adb fix: null type error 2021-12-29 01:01:22 +08:00
GyDi
6570784b46 chore: dev support macos 2021-12-29 01:01:09 +08:00
GyDi
63b31f41a7 v0.0.4 2021-12-28 01:48:25 +08:00
GyDi
96eca59afc feat: record selected proxy 2021-12-28 01:47:43 +08:00
GyDi
25978bde8e feat: display version 2021-12-28 01:35:58 +08:00
GyDi
7691d2b5db chore: save lock file 2021-12-28 00:14:15 +08:00
GyDi
edb108322e chore: post version script 2021-12-27 23:07:56 +08:00
GyDi
af3936b68e v0.0.3 2021-12-27 23:06:46 +08:00
GyDi
e76c25f9d3 v0.0.3 2021-12-27 23:06:45 +08:00
GyDi
124f455be2 chore: update version 2021-12-27 10:21:49 +08:00
GyDi
f679eefe0a chore: update version 2021-12-27 10:21:10 +08:00
162 changed files with 21051 additions and 9678 deletions

View File

@ -2,6 +2,7 @@ name: 问题反馈 / Bug report
title: "[BUG] "
description: 反馈你遇到的问题 / Report the issue you are experiencing
labels: ["bug"]
type: "Bug"
body:
- type: markdown
@ -11,14 +12,16 @@ 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. 请 **务必** 按照模板规范详细描述问题否则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将会被直接关闭
## 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 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
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
- type: textarea
id: description
@ -56,7 +59,7 @@ body:
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")
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")
validations:
required: true

View File

@ -2,6 +2,7 @@ name: 功能请求 / Feature request
title: "[Feature] "
description: 提出你的功能请求 / Propose your feature request
labels: ["enhancement"]
type: "Feature"
body:
- type: markdown
@ -33,3 +34,14 @@ body:
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
validations:
required: true
- type: checkboxes
id: os-labels
attributes:
label: 适用系统 / Target OS
description: 请选择该功能适用的操作系统(至少选择一个) / Please select the operating system(s) for this feature request (select at least one)
options:
- label: windows
- label: macos
- label: linux
validations:
required: true

View File

@ -2,6 +2,9 @@ name: Alpha Build
on:
workflow_dispatch:
schedule:
# UTC+8 0,6,12,18
- cron: "0 16,22,4,10 * * *"
permissions: write-all
env:
CARGO_INCREMENTAL: 0
@ -12,7 +15,201 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check_commit:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check if version changed or src changed
id: check
run: |
# For manual workflow_dispatch, always run
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
# Store current version from package.json
CURRENT_VERSION=$(cat package.json | jq -r '.version')
echo "Current version: $CURRENT_VERSION"
# Get the previous commit's package.json version
git checkout HEAD~1 package.json
PREVIOUS_VERSION=$(cat package.json | jq -r '.version')
echo "Previous version: $PREVIOUS_VERSION"
# Reset back to current commit
git checkout HEAD package.json
# Check if version changed
if [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then
echo "Version changed from $PREVIOUS_VERSION to $CURRENT_VERSION"
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check if src or src-tauri directories changed
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
delete_old_assets:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Delete Old Alpha Release Assets
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const releaseTag = 'alpha';
try {
// Get the release by tag name
const { data: release } = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: releaseTag
});
console.log(`Found release with ID: ${release.id}`);
// Delete each asset
if (release.assets && release.assets.length > 0) {
console.log(`Deleting ${release.assets.length} assets`);
for (const asset of release.assets) {
console.log(`Deleting asset: ${asset.name} (${asset.id})`);
await github.rest.repos.deleteReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: asset.id
});
}
console.log('All assets deleted successfully');
} else {
console.log('No assets found to delete');
}
} catch (error) {
if (error.status === 404) {
console.log('Release not found, nothing to delete');
} else {
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 Alpha update logs
id: fetch_alpha_logs
run: |
# Check if UPDATELOG.md exists
if [ -f "UPDATELOG.md" ]; then
# Extract the section starting with ## and containing -alpha until the next ## or end of file
# ALPHA_LOGS=$(awk '/^## .*-alpha/{flag=1; print; next} /^## /{flag=0} flag' UPDATELOG.md)
ALPHA_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$ALPHA_LOGS" ]; then
echo "Found alpha update logs"
echo "ALPHA_LOGS<<EOF" >> $GITHUB_ENV
echo "$ALPHA_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No alpha 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: |
# 检查 ALPHA_LOGS 是否存在,如果不存在则使用默认消息
if [ -z "$ALPHA_LOGS" ]; then
echo "No alpha logs found, using default message"
ALPHA_LOGS="More new features are now supported. Check for detailed changelog soon."
else
echo "Using found alpha logs"
fi
# 生成 release.txt 文件
cat > release.txt << EOF
$ALPHA_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: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
alpha:
needs: update_tag
strategy:
fail-fast: false
matrix:
@ -50,7 +247,7 @@ jobs:
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
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
@ -67,8 +264,8 @@ jobs:
pnpm i
pnpm check ${{ matrix.target }}
- name: Alpha Version update
run: pnpm run fix-alpha-version
- name: Release Alpha Version
run: pnpm release-alpha-version
- name: Tauri build
uses: tauri-apps/tauri-action@v0
@ -92,13 +289,8 @@ jobs:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Portable Bundle
if: matrix.os == 'windows-latest'
run: pnpm portable ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
alpha-for-linux-arm:
needs: update_tag
strategy:
fail-fast: false
matrix:
@ -141,6 +333,9 @@ jobs:
pnpm i
pnpm check ${{ matrix.target }}
- name: Release Alpha Version
run: pnpm release-alpha-version
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
@ -164,6 +359,7 @@ 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 }} \
@ -212,7 +408,6 @@ jobs:
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
@ -220,6 +415,7 @@ jobs:
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-for-fixed-webview2:
needs: update_tag
strategy:
fail-fast: false
matrix:
@ -260,6 +456,9 @@ jobs:
pnpm i
pnpm check ${{ matrix.target }}
- name: Release Alpha Version
run: pnpm release-alpha-version
- 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
@ -293,9 +492,9 @@ jobs:
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip.sig"
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip\.sig$", "_fixed_webview2-setup.nsis.zip.sig"
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}
@ -304,7 +503,6 @@ jobs:
with:
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*
@ -313,65 +511,3 @@ jobs:
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: [alpha, alpha-for-linux-arm, alpha-for-fixed-webview2]
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 用户请查看下面FAQ中的解决方案)
#### 正常版本(推荐)
- 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

@ -6,6 +6,10 @@ permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
concurrency:
# only allow per workflow per commit (and not pr) to run at a time
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
release:
@ -40,12 +44,13 @@ jobs:
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
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
@ -82,12 +87,6 @@ jobs:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Portable Bundle
if: matrix.os == 'windows-latest'
run: pnpm portable ${{ matrix.target }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-for-linux-arm:
strategy:
fail-fast: false
@ -154,6 +153,7 @@ 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 }} \
@ -216,9 +216,6 @@ jobs:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: i686-pc-windows-msvc
arch: x86
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
@ -234,6 +231,8 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
@ -283,9 +282,9 @@ jobs:
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip.sig"
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip\.sig$", "_fixed_webview2-setup.nsis.zip.sig"
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}

View File

@ -1 +1,16 @@
pnpm pretty-quick --staged
#!/bin/bash
#pnpm pretty-quick --staged
# 运行 clippy fmt
cargo fmt --manifest-path ./src-tauri/Cargo.toml
if [ $? -ne 0 ]; then
echo "rustfmt failed to format the code. Please fix the issues and try again."
exit 1
fi
#git add .
# 允许提交
exit 0

13
.husky/pre-push Normal file
View File

@ -0,0 +1,13 @@
#!/bin/bash
# 运行 clippy
# cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
# 如果 clippy 失败,阻止 push
# if [ $? -ne 0 ]; then
# echo "Clippy found issues in sub_crate. Please fix them before pushing."
# exit 1
# fi
# 允许 push
exit 0

View File

@ -38,7 +38,7 @@ npm install pnpm -g
pnpm install
```
### Download the Clash Mihomo Core Binary
### Download the Mihomo Core Binary
You have two options for downloading the clash binary:
@ -48,7 +48,7 @@ You have two options for downloading the clash binary:
# Use '--force' to force update to the latest version
# pnpm run check --force
```
- Manually download it from the [Clash Meta release](https://github.com/MetaCubeX/Clash.Meta/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
- Manually download it from the [Mihomo release](https://github.com/MetaCubeX/mihomo/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
### Run the Development Server
@ -65,11 +65,35 @@ pnpm dev:diff
To build this project:
```shell
pnpm run build
pnpm build
```
For a faster build, use the following command
```shell
pnpm build:fast
```
This uses Rust's fast-release profile which significantly reduces compilation time by disabling optimization and LTO. The resulting binary will be larger and less performant than the standard build, but it's useful for testing changes quickly.
The `Artifacts` will display in the `log` in the Terminal.
### Build clean
To clean rust build:
```shell
pnpm clean
```
### Portable Version (Windows Only)
To package portable version after the build:
```shell
pnpm portable
```
## Contributing Your Changes
Once you have made your changes:

View File

@ -1,3 +1,174 @@
## 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
**发行代号:臻**
@ -6,14 +177,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
@ -23,7 +194,7 @@
- 修复Linux下编译问题
- 修复热键无法关闭面板的问题
## v2.1.0 - 发行代号:臻
##### 2.1.0 - 发行代号:臻
### 功能新增
@ -101,6 +272,8 @@
- 改进脚本验证与异常处理流程
- 修复编译警告(移除无用导入)
---
## v2.0.3
### Notice
@ -148,6 +321,8 @@
- 改进更新托盘图标性能
- 窗口隐藏后WebSocket断开连接
---
## v2.0.2
### Notice

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@ -1,11 +1,12 @@
{
"name": "clash-verge",
"version": "2.1.2",
"version": "2.2.3",
"license": "GPL-3.0-only",
"scripts": {
"dev": "cross-env RUST_BACKTRACE=1 tauri 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",
@ -15,8 +16,11 @@
"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/alpha_version.mjs",
"prepare": "husky"
"fix-alpha-version": "node scripts/fix-alpha_version.mjs",
"release-version": "node scripts/release_version.mjs",
"release-alpha-version": "node scripts/release-alpha_version.mjs",
"prepare": "husky",
"clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@ -25,46 +29,49 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^6.4.2",
"@mui/icons-material": "^6.4.8",
"@mui/lab": "6.0.0-beta.25",
"@mui/material": "^6.4.2",
"@mui/x-data-grid": "^7.25.0",
"@mui/material": "^6.4.8",
"@mui/x-data-grid": "^7.28.0",
"@tauri-apps/api": "2.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.1",
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
"@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-notification": "^2.2.2",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "2.2.0",
"@tauri-apps/plugin-updater": "2.3.0",
"@types/d3-shape": "^3.1.7",
"@types/json-schema": "^7.0.15",
"ahooks": "^3.8.4",
"axios": "^1.7.9",
"axios": "^1.8.3",
"cli-color": "^2.0.4",
"d3-shape": "^3.2.0",
"dayjs": "1.11.13",
"foxact": "^0.2.43",
"foxact": "^0.2.44",
"glob": "^11.0.1",
"i18next": "^24.2.2",
"i18next": "^24.2.3",
"js-base64": "^3.7.7",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.52.2",
"monaco-yaml": "^5.2.3",
"nanoid": "^5.0.9",
"monaco-yaml": "^5.3.1",
"nanoid": "^5.1.5",
"peggy": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.12",
"react-error-boundary": "^4.1.2",
"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-markdown": "^9.1.0",
"react-monaco-editor": "^0.56.2",
"react-router-dom": "^6.30.0",
"react-transition-group": "^4.4.5",
"react-virtuoso": "^4.6.3",
"react-virtuoso": "^4.12.5",
"recharts": "^2.15.1",
"sockette": "^2.0.6",
"swr": "^2.3.0",
"swr": "^2.3.3",
"tar": "^7.4.3",
"types-pac": "^1.0.3",
"zustand": "^5.0.3"
@ -78,20 +85,20 @@
"@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",
"@vitejs/plugin-legacy": "^6.0.2",
"@vitejs/plugin-react": "^4.3.4",
"adm-zip": "^0.5.16",
"cross-env": "^7.0.3",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"meta-json-schema": "^1.19.1",
"meta-json-schema": "^1.19.3",
"node-fetch": "^3.3.2",
"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",
"prettier": "^3.5.3",
"pretty-quick": "^4.1.1",
"sass": "^1.86.0",
"terser": "^5.39.0",
"typescript": "^5.8.2",
"vite": "^6.2.2",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svgr": "^4.3.0"
},

7608
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -33,22 +33,24 @@ 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);
const initversion = packageJson.version;
// 将匹配到的第一个 "alpha" => 具体的hash
const fixversion = initversion.replace("alpha", newVersion);
packageJson.version = fixversion;
// 获取键值替换
let result = packageJson.version.replace("alpha", newVersion);
console.log("[INFO]: Current version is: ", result);
packageJson.version = result;
// 写入版本号
await fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
"utf8",
);
console.log(`Alpha version update to: ${fixversion}`);
console.log(`[INFO]: Alpha version update to: ${newVersion}`);
} catch (error) {
console.error("pnpm run fix-alpha-version ERROR", error);
}
}
const newVersion = await getLatestCommitHash();
updatePackageVersion(newVersion);
updatePackageVersion(newVersion).catch(console.error);

View File

@ -2,20 +2,16 @@ import fs from "fs";
import path from "path";
import AdmZip from "adm-zip";
import { createRequire } from "module";
import { getOctokit, context } from "@actions/github";
import fsp from "fs/promises";
const target = process.argv.slice(2)[0];
const alpha = process.argv.slice(2)[1];
const ARCH_MAP = {
"x86_64-pc-windows-msvc": "x64",
"i686-pc-windows-msvc": "x86",
"aarch64-pc-windows-msvc": "arm64",
};
const PROCESS_MAP = {
x64: "x64",
ia32: "x86",
arm64: "arm64",
};
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
@ -37,10 +33,9 @@ async function resolvePortable() {
if (!fs.existsSync(path.join(configDir, "PORTABLE"))) {
await fsp.writeFile(path.join(configDir, "PORTABLE"), "");
}
const zip = new AdmZip();
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-verge.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
@ -49,46 +44,9 @@ async function resolvePortable() {
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
const { version } = packageJson;
const zipFile = `Clash.Verge_${version}_${arch}_portable.zip`;
zip.writeZip(zipFile);
console.log("[INFO]: create portable zip successfully");
// push release assets
if (process.env.GITHUB_TOKEN === undefined) {
throw new Error("GITHUB_TOKEN is required");
}
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
console.log("[INFO]: upload to ", tag);
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag,
});
let assets = release.assets.filter((x) => {
return x.name === zipFile;
});
if (assets.length > 0) {
let id = assets[0].id;
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: id,
});
}
console.log(release.name);
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: release.id,
name: zipFile,
data: zip.toBuffer(),
});
}
resolvePortable().catch(console.error);

View File

@ -0,0 +1,96 @@
import fs from "fs/promises";
import path from "path";
/**
* 更新 package.json 文件中的版本号
*/
async function updatePackageVersion() {
const _dirname = process.cwd();
const packageJsonPath = path.join(_dirname, "package.json");
try {
const data = await fs.readFile(packageJsonPath, "utf8");
const packageJson = JSON.parse(data);
let result = packageJson.version;
if (!result.includes("alpha")) {
result = `${result}-alpha`;
}
console.log("[INFO]: Current package.json version is: ", result);
packageJson.version = result;
await fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
"utf8",
);
console.log(`[INFO]: package.json version updated to: ${result}`);
} catch (error) {
console.error("Error updating package.json version:", error);
}
}
/**
* 更新 Cargo.toml 文件中的版本号
*/
async function updateCargoVersion() {
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 updatedLines = lines.map((line) => {
if (line.startsWith("version =")) {
const versionMatch = line.match(/version\s*=\s*"([^"]+)"/);
if (versionMatch && !versionMatch[1].includes("alpha")) {
const newVersion = `${versionMatch[1]}-alpha`;
return line.replace(versionMatch[1], newVersion);
}
}
return line;
});
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
} catch (error) {
console.error("Error updating Cargo.toml version:", error);
}
}
/**
* 更新 tauri.conf.json 文件中的版本号
*/
async function updateTauriConfigVersion() {
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);
let version = tauriConfig.version;
if (!version.includes("alpha")) {
version = `${version}-alpha`;
}
console.log("[INFO]: Current tauri.conf.json version is: ", version);
tauriConfig.version = version;
await fs.writeFile(
tauriConfigPath,
JSON.stringify(tauriConfig, null, 2),
"utf8",
);
console.log(`[INFO]: tauri.conf.json version updated to: ${version}`);
} catch (error) {
console.error("Error updating tauri.conf.json version:", error);
}
}
/**
* 主函数依次更新所有文件的版本号
*/
async function main() {
await updatePackageVersion();
await updateCargoVersion();
await updateTauriConfigVersion();
}
main().catch(console.error);

197
scripts/release_version.mjs Normal file
View File

@ -0,0 +1,197 @@
import fs from "fs/promises";
import path from "path";
import { program } from "commander";
/**
* 验证版本号格式
* @param {string} version
* @returns {boolean}
*/
function isValidVersion(version) {
return /^v?\d+\.\d+\.\d+(-(alpha|beta|rc)(\.\d+)?)?$/i.test(version);
}
/**
* 标准化版本号确保v前缀可选
* @param {string} version
* @returns {string}
*/
function normalizeVersion(version) {
return version.startsWith("v") ? version : `v${version}`;
}
/**
* 更新 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}`,
);
return 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 updatedLines = lines.map((line) => {
if (line.trim().startsWith("version =")) {
return line.replace(
/version\s*=\s*"[^"]+"/,
`version = "${versionWithoutV}"`,
);
}
return line;
});
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
console.log(`[INFO]: Cargo.toml version updated to: ${versionWithoutV}`);
} 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;
console.log(
"[INFO]: Current tauri.conf.json version is: ",
tauriConfig.version,
);
tauriConfig.version = versionWithoutV;
await fs.writeFile(
tauriConfigPath,
JSON.stringify(tauriConfig, null, 2),
"utf8",
);
console.log(
`[INFO]: tauri.conf.json version updated to: ${versionWithoutV}`,
);
} catch (error) {
console.error("Error updating tauri.conf.json version:", error);
throw error;
}
}
/**
* 获取当前版本号从package.json
*/
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;
}
}
/**
* 主函数更新所有文件的版本号
* @param {string} versionArg 版本参数可以是标签或完整版本号
*/
async function main(versionArg) {
if (!versionArg) {
console.error("Error: Version argument is required");
process.exit(1);
}
try {
let newVersion;
const validTags = ["alpha", "beta", "rc"];
// 判断参数是标签还是完整版本号
if (validTags.includes(versionArg.toLowerCase())) {
// 标签模式:在当前版本基础上添加标签
const currentVersion = await getCurrentVersion();
const baseVersion = currentVersion.replace(
/-(alpha|beta|rc)(\.\d+)?$/i,
"",
);
newVersion = `${baseVersion}-${versionArg.toLowerCase()}`;
} else {
// 完整版本号模式
if (!isValidVersion(versionArg)) {
console.error(
"Error: Invalid version format. Expected format: vX.X.X or vX.X.X-tag (e.g. v2.2.3 or v2.2.3-alpha)",
);
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);
}
}
// Example:
// pnpm release-version 2.2.3-alpha
// 设置命令行界面
program
.name("pnpm release-version")
.description(
"Update project version numbers. Can add tag (alpha/beta/rc) or set full version (e.g. v2.2.3 or v2.2.3-alpha)",
)
.argument(
"<version>",
"version tag (alpha/beta/rc) or full version (e.g. v2.2.3 or v2.2.3-alpha)",
)
.action(main)
.parse(process.argv);

View File

@ -43,3 +43,42 @@ 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,10 +1,15 @@
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog } from "./updatelog.mjs";
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
// Add stable update JSON filenames
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update.json";
const UPDATE_JSON_PROXY = "update-proxy.json";
// Add alpha update JSON filenames
const ALPHA_TAG_NAME = "updater-alpha";
const ALPHA_UPDATE_JSON_FILE = "update.json";
const ALPHA_UPDATE_JSON_PROXY = "update-proxy.json";
/// generate update.json
/// upload to update tag's release asset
@ -16,26 +21,74 @@ async function resolveUpdater() {
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const { data: tags } = await github.rest.repos.listTags({
// Fetch all tags using pagination
let allTags = [];
let page = 1;
const perPage = 100;
while (true) {
const { data: pageTags } = await github.rest.repos.listTags({
...options,
per_page: 10,
page: 1,
per_page: perPage,
page: page,
});
// get the latest publish tag
const tag = tags.find((t) => t.name.startsWith("v"));
allTags = allTags.concat(pageTags);
console.log(tag);
// Break if we received fewer tags than requested (last page)
if (pageTags.length < perPage) {
break;
}
page++;
}
const tags = allTags;
console.log(`Retrieved ${tags.length} tags in total`);
// More flexible tag detection with regex patterns
const stableTagRegex = /^v\d+\.\d+\.\d+$/; // Matches vX.Y.Z format
// const preReleaseRegex = /^v\d+\.\d+\.\d+-(alpha|beta|rc|pre)/i; // Matches vX.Y.Z-alpha/beta/rc format
const preReleaseRegex = /^(alpha|beta|rc|pre)$/i; // Matches exact alpha/beta/rc/pre tags
// Get the latest stable tag and pre-release tag
const stableTag = tags.find((t) => stableTagRegex.test(t.name));
const preReleaseTag = tags.find((t) => preReleaseRegex.test(t.name));
console.log("All tags:", tags.map((t) => t.name).join(", "));
console.log("Stable tag:", stableTag ? stableTag.name : "None found");
console.log(
"Pre-release tag:",
preReleaseTag ? preReleaseTag.name : "None found",
);
console.log();
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
// Process stable release
if (stableTag) {
await processRelease(github, options, stableTag, false);
}
// Process pre-release if found
if (preReleaseTag) {
await processRelease(github, options, preReleaseTag, true);
}
}
// Process a release (stable or alpha) and generate update files
async function processRelease(github, options, tag, isAlpha) {
if (!tag) return;
try {
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name), // use updatelog.md
notes: await resolveUpdateLog(tag.name).catch(() =>
resolveUpdateLogDefault().catch(() => "No changelog available"),
),
pub_date: new Date().toISOString(),
platforms: {
win64: { signature: "", url: "" }, // compatible with older formats
@ -56,9 +109,10 @@ async function resolveUpdater() {
},
};
const promises = latestRelease.assets.map(async (asset) => {
const promises = release.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
// Process all the platform URL and signature data
// win64 url
if (name.endsWith("x64-setup.exe")) {
updateData.platforms.win64.url = browser_download_url;
@ -143,8 +197,7 @@ async function resolveUpdater() {
}
});
// 生成一个代理github的更新文件
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
// Generate a proxy update file for accelerated GitHub resources
const updateDataNew = JSON.parse(JSON.stringify(updateData));
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
@ -156,42 +209,105 @@ async function resolveUpdater() {
}
});
// update the update.json
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: UPDATE_TAG_NAME,
});
// Get the appropriate updater release based on isAlpha flag
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME;
console.log(
`Processing ${isAlpha ? "alpha" : "stable"} release:`,
releaseTag,
);
// delete the old assets
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 === UPDATE_JSON_FILE) {
if (asset.name === jsonFile) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === UPDATE_JSON_PROXY) {
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
// Upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_FILE,
name: jsonFile,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_PROXY,
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,
);
}
} catch (error) {
if (error.status === 404) {
console.log(`Release not found for tag: ${tag.name}, skipping...`);
} else {
console.error(
`Failed to get release for tag: ${tag.name}`,
error.message,
);
}
}
}
// get the signature file content

1
src-tauri/.clippy.toml Normal file
View File

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

2465
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.1.2"
version = "2.2.3"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@ -13,67 +13,72 @@ build = "build.rs"
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "2.0.5", features = [] }
tauri-build = { version = "2.1.0", features = [] }
[dependencies]
warp = "0.3"
anyhow = "1.0"
anyhow = "1.0.97"
dirs = "6.0"
open = "5.1"
open = "5.3"
log = "0.4"
dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4"
sysinfo = "0.33.1"
chrono = "0.4.40"
sysinfo = "0.34"
boa_engine = "0.20.0"
serde_json = "1.0"
serde_yaml = "0.9"
once_cell = "1.19"
once_cell = "1.21.3"
port_scanner = "0.1.5"
delay_timer = "0.11.6"
parking_lot = "0.12"
percent-encoding = "2.3.1"
window-shadows = { version = "0.2.2" }
tokio = { version = "1.43", features = ["full"] }
tokio = { version = "1.44", features = [
"rt-multi-thread",
"macros",
"time",
"sync",
] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
regex = "1.11.1"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
image = "0.24"
imageproc = "0.23"
rusttype = "0.9"
tauri = { version = "2.2.5", features = [
image = "0.25.6"
imageproc = "0.25.0"
tauri = { version = "2.4.0", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
network-interface = { version = "2.0.0", features = ["serde"] }
network-interface = { version = "2.0.1", 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-clipboard-manager = "2.2.2"
tauri-plugin-deep-link = "2.2.0"
tauri-plugin-devtools = "2.0.0-rc"
url = "2.5.4"
zip = "2.2.2"
reqwest_dav = "0.1.14"
tauri-plugin-devtools = "2.0.0"
zip = "2.5.0"
reqwest_dav = "0.1.15"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.2"
tokio-tungstenite = "0.26.1"
getrandom = "0.3.2"
tokio-tungstenite = "0.26.2"
futures = "0.3"
sys-locale = "0.3.1"
sys-locale = "0.3.2"
async-trait = "0.1.88"
mihomo_api = { path = "src_crates/crate_mihomo_api" }
ab_glyph = "0.2.29"
tungstenite = "0.26.2"
libc = "0.2"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
deelevate = "0.2.0"
winreg = "0.55.0"
url = "2.5.4"
[target.'cfg(target_os = "linux")'.dependencies]
users = "0.11.0"
@ -81,9 +86,8 @@ users = "0.11.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.2.0"
tauri-plugin-global-shortcut = "2.2.0"
tauri-plugin-updater = "2.3.0"
tauri-plugin-updater = "2.6.1"
tauri-plugin-window-state = "2.2.1"
#openssl
[features]
default = ["custom-protocol"]
@ -100,6 +104,34 @@ strip = true
[profile.dev]
incremental = true
[profile.fast-release]
inherits = "release" # 继承 release 的配置
panic = "abort" # 与 release 相同
codegen-units = 256 # 增加编译单元,提升编译速度
lto = false # 禁用 LTO提升编译速度
opt-level = 0 # 禁用优化,大幅提升编译速度
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[profile.fast-dev]
inherits = "dev" # 继承 dev 的配置
codegen-units = 256 # 增加编译单元,提升编译速度
opt-level = 0 # 禁用优化,进一步提升编译速度
incremental = true # 启用增量编译
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[dev-dependencies]
tempfile = "3.19.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" }

View File

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

216
src-tauri/src/cmd/app.rs Normal file
View File

@ -0,0 +1,216 @@
use super::CmdResult;
use crate::{
feat, logging,
utils::{dirs, logging::Type},
wrap_err,
};
use tauri::Manager;
/// 打开应用程序所在目录
#[tauri::command]
pub fn open_app_dir() -> CmdResult<()> {
let app_dir = wrap_err!(dirs::app_home_dir())?;
wrap_err!(open::that(app_dir))
}
/// 打开核心所在目录
#[tauri::command]
pub fn open_core_dir() -> CmdResult<()> {
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
wrap_err!(open::that(core_dir))
}
/// 打开日志目录
#[tauri::command]
pub fn open_logs_dir() -> CmdResult<()> {
let log_dir = wrap_err!(dirs::app_logs_dir())?;
wrap_err!(open::that(log_dir))
}
/// 打开网页链接
#[tauri::command]
pub fn open_web_url(url: String) -> CmdResult<()> {
wrap_err!(open::that(url))
}
/// 打开/关闭开发者工具
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_webview_window("main") {
if !window.is_devtools_open() {
window.open_devtools();
} else {
window.close_devtools();
}
}
}
/// 退出应用
#[tauri::command]
pub fn exit_app() {
feat::quit(Some(0));
}
/// 重启应用
#[tauri::command]
pub async fn restart_app() -> CmdResult<()> {
feat::restart_app();
Ok(())
}
/// 获取便携版标识
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
}
/// 获取应用目录
#[tauri::command]
pub fn get_app_dir() -> CmdResult<String> {
let app_home_dir = wrap_err!(dirs::app_home_dir())?
.to_string_lossy()
.to_string();
Ok(app_home_dir)
}
/// 获取当前自启动状态
#[tauri::command]
pub 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()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let is_image = content_type.starts_with("image/");
// 获取响应内容
let content = wrap_err!(response.bytes().await)?;
// 检查内容是否为HTML (针对CDN错误页面)
let is_html = content.len() > 15
&& (content.starts_with(b"<!DOCTYPE html")
|| content.starts_with(b"<html")
|| content.starts_with(b"<?xml"));
// 只有当内容确实是图片时才保存
if is_image && !is_html {
{
let mut file = match std::fs::File::create(&temp_path) {
Ok(file) => file,
Err(_) => {
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
} else {
return Err("Failed to create temporary file".into());
}
}
};
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {}
Err(_) => {
let _ = std::fs::remove_file(&temp_path);
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
}
}
}
} else {
let _ = std::fs::remove_file(&temp_path);
}
Ok(icon_path.to_string_lossy().to_string())
} else {
let _ = std::fs::remove_file(&temp_path);
Err(format!("下载的内容不是有效图片: {}", url))
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct IconInfo {
name: String,
previous_t: String,
current_t: String,
}
/// 复制图标文件
#[tauri::command]
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::{fs, path::Path};
let file_path = Path::new(&path);
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
if !icon_dir.exists() {
let _ = fs::create_dir_all(&icon_dir);
}
let ext = match file_path.extension() {
Some(e) => e.to_string_lossy().to_string(),
None => "ico".to_string(),
};
let dest_path = icon_dir.join(format!(
"{0}-{1}.{ext}",
icon_info.name, icon_info.current_t
));
if file_path.exists() {
if icon_info.previous_t.trim() != "" {
fs::remove_file(
icon_dir.join(format!("{0}-{1}.png", icon_info.name, icon_info.previous_t)),
)
.unwrap_or_default();
fs::remove_file(
icon_dir.join(format!("{0}-{1}.ico", icon_info.name, icon_info.previous_t)),
)
.unwrap_or_default();
}
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()),
}
} else {
Err("file not found".to_string())
}
}

223
src-tauri/src/cmd/clash.rs Normal file
View File

@ -0,0 +1,223 @@
use super::CmdResult;
use crate::{config::*, core::*, feat, module::mihomo::MihomoManager, wrap_err};
use serde_yaml::Mapping;
/// 复制Clash环境变量
#[tauri::command]
pub fn copy_clash_env() -> CmdResult {
feat::copy_clash_env();
Ok(())
}
/// 获取Clash信息
#[tauri::command]
pub fn get_clash_info() -> CmdResult<ClashInfo> {
Ok(Config::clash().latest().get_client_info())
}
/// 修改Clash配置
#[tauri::command]
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
wrap_err!(feat::patch_clash(payload).await)
}
/// 修改Clash模式
#[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult {
feat::change_clash_mode(payload);
Ok(())
}
/// 切换Clash核心
#[tauri::command]
pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>> {
log::info!(target: "app", "changing core to {clash_core}");
match CoreManager::global()
.change_core(Some(clash_core.clone()))
.await
{
Ok(_) => {
log::info!(target: "app", "core changed to {clash_core}");
handle::Handle::notice_message("config_core::change_success", &clash_core);
handle::Handle::refresh_clash();
Ok(None)
}
Err(err) => {
let error_msg = err.to_string();
log::error!(target: "app", "failed to change core: {error_msg}");
handle::Handle::notice_message("config_core::change_error", &error_msg);
Ok(Some(error_msg))
}
}
}
/// 重启核心
#[tauri::command]
pub async fn restart_core() -> CmdResult {
wrap_err!(CoreManager::global().restart_core().await)
}
/// 获取代理延迟
#[tauri::command]
pub async fn clash_api_get_proxy_delay(
name: String,
url: Option<String>,
timeout: i32,
) -> CmdResult<serde_json::Value> {
MihomoManager::global()
.test_proxy_delay(&name, url, timeout)
.await
}
/// 测试URL延迟
#[tauri::command]
pub async fn test_delay(url: String) -> CmdResult<u32> {
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
}
/// 保存DNS配置到单独文件
#[tauri::command]
pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
use crate::utils::dirs;
use serde_yaml;
use std::fs;
// 获取DNS配置文件路径
let dns_path = dirs::app_home_dir()
.map_err(|e| e.to_string())?
.join("dns_config.yaml");
// 保存DNS配置到文件
let yaml_str = serde_yaml::to_string(&dns_config).map_err(|e| e.to_string())?;
fs::write(&dns_path, yaml_str).map_err(|e| e.to_string())?;
log::info!(target: "app", "DNS config saved to {:?}", dns_path);
Ok(())
}
/// 应用或撤销DNS配置
#[tauri::command]
pub fn apply_dns_config(apply: bool) -> CmdResult {
use crate::{
config::Config,
core::{handle, CoreManager},
utils::dirs,
};
use tauri::async_runtime;
// 使用spawn来处理异步操作
async_runtime::spawn(async move {
if apply {
// 读取DNS配置文件
let dns_path = match dirs::app_home_dir() {
Ok(path) => path.join("dns_config.yaml"),
Err(e) => {
log::error!(target: "app", "Failed to get home dir: {}", e);
return;
}
};
if !dns_path.exists() {
log::warn!(target: "app", "DNS config file not found");
return;
}
let dns_yaml = match std::fs::read_to_string(&dns_path) {
Ok(content) => content,
Err(e) => {
log::error!(target: "app", "Failed to read DNS config: {}", e);
return;
}
};
// 解析DNS配置并创建patch
let patch_config = match serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {
Ok(config) => {
let mut patch = serde_yaml::Mapping::new();
patch.insert("dns".into(), config.into());
patch
}
Err(e) => {
log::error!(target: "app", "Failed to parse DNS config: {}", e);
return;
}
};
log::info!(target: "app", "Applying DNS config from file");
// 重新生成配置确保DNS配置被正确应用
// 这里不调用patch_clash以避免将DNS配置写入config.yaml
Config::runtime()
.latest()
.patch_config(patch_config.clone());
// 首先重新生成配置
if let Err(err) = Config::generate().await {
log::error!(target: "app", "Failed to regenerate config with DNS: {}", err);
return;
}
// 然后应用新配置
if let Err(err) = CoreManager::global().update_config().await {
log::error!(target: "app", "Failed to apply config with DNS: {}", err);
} else {
log::info!(target: "app", "DNS config successfully applied");
handle::Handle::refresh_clash();
}
} else {
// 当关闭DNS设置时不需要对配置进行任何修改
// 直接重新生成配置让enhance函数自动跳过DNS配置的加载
log::info!(target: "app", "DNS settings disabled, regenerating config");
// 重新生成配置
if let Err(err) = Config::generate().await {
log::error!(target: "app", "Failed to regenerate config: {}", err);
return;
}
// 应用新配置
match CoreManager::global().update_config().await {
Ok(_) => {
log::info!(target: "app", "Config regenerated successfully");
handle::Handle::refresh_clash();
}
Err(err) => {
log::error!(target: "app", "Failed to apply regenerated config: {}", err);
}
}
}
});
Ok(())
}
/// 检查DNS配置文件是否存在
#[tauri::command]
pub fn check_dns_config_exists() -> CmdResult<bool> {
use crate::utils::dirs;
let dns_path = dirs::app_home_dir()
.map_err(|e| e.to_string())?
.join("dns_config.yaml");
Ok(dns_path.exists())
}
/// 获取DNS配置文件内容
#[tauri::command]
pub async fn get_dns_config_content() -> CmdResult<String> {
use crate::utils::dirs;
use std::fs;
let dns_path = dirs::app_home_dir()
.map_err(|e| e.to_string())?
.join("dns_config.yaml");
if !dns_path.exists() {
return Err("DNS config file not found".into());
}
let content = fs::read_to_string(&dns_path).map_err(|e| e.to_string())?;
Ok(content)
}

View File

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

File diff suppressed because it is too large Load Diff

38
src-tauri/src/cmd/mod.rs Normal file
View File

@ -0,0 +1,38 @@
use anyhow::Result;
// Common result type used by command functions
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 runtime;
pub mod save_profile;
pub mod service;
pub mod system;
pub mod uwp;
pub mod validate;
pub mod verge;
pub mod webdav;
// 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 runtime::*;
pub use save_profile::*;
pub use service::*;
pub use system::*;
pub use uwp::*;
pub use validate::*;
pub use verge::*;
pub use webdav::*;

View File

@ -0,0 +1,63 @@
use super::CmdResult;
use crate::wrap_err;
use network_interface::NetworkInterface;
use serde_yaml::Mapping;
use sysproxy::{Autoproxy, Sysproxy};
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Sysproxy::get_system_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert(
"server".into(),
format!("{}:{}", current.host, current.port).into(),
);
map.insert("bypass".into(), current.bypass.into());
Ok(map)
}
/// get the system proxy
#[tauri::command]
pub fn get_auto_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Autoproxy::get_auto_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert("url".into(), current.url.into());
Ok(map)
}
/// 获取网络接口列表
#[tauri::command]
pub fn get_network_interfaces() -> Vec<String> {
use sysinfo::Networks;
let mut result = Vec::new();
let networks = Networks::new_with_refreshed_list();
for (interface_name, _) in &networks {
result.push(interface_name.clone());
}
result
}
/// 获取网络接口详细信息
#[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?;
let mut result = Vec::new();
for interface in interfaces {
if names.contains(&interface.name) {
result.push(interface);
}
}
Ok(result)
}

View File

@ -0,0 +1,244 @@
use super::CmdResult;
use crate::{
config::{Config, IProfiles, PrfItem, PrfOption},
core::{handle, tray::Tray, CoreManager},
feat, logging, ret_err,
utils::{dirs, help, logging::Type},
wrap_err,
};
/// 获取配置文件列表
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
let _ = 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(())
}
/// 导入配置文件
#[tauri::command]
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
/// 重新排序配置文件
#[tauri::command]
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
}
/// 创建配置文件
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
/// 更新配置文件
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option).await)
}
/// 删除配置文件
#[tauri::command]
pub async fn delete_profile(index: String) -> CmdResult {
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
if should_update {
wrap_err!(CoreManager::global().update_config().await)?;
handle::Handle::refresh_clash();
}
Ok(())
}
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复
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 profiles_config = Config::profiles();
let profiles_data = profiles_config.latest();
let config_file_result = 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);
}
match std::fs::read_to_string(&file_path) {
Ok(content) => match serde_yaml::from_str::<serde_yaml::Value>(&content) {
Ok(_) => {
logging!(info, Type::Cmd, true, "目标配置文件语法正确");
}
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(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);
}
}
}
}
}
// 更新profiles配置
logging!(info, Type::Cmd, true, "正在更新配置草稿");
let _ = Config::profiles().draft().patch_config(profiles);
// 更新配置并进行验证
match CoreManager::global().update_config().await {
Ok((true, _)) => {
logging!(info, Type::Cmd, true, "配置更新成功");
handle::Handle::refresh_clash();
let _ = Tray::global().update_tooltip();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(true)
}
Ok((false, error_msg)) => {
logging!(warn, Type::Cmd, true, "配置验证失败: {}", error_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();
wrap_err!(Config::profiles().data().save_file())?;
logging!(info, Type::Cmd, true, "成功恢复到之前的配置");
}
// 发送验证错误通知
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(false)
}
Err(e) => {
logging!(warn, Type::Cmd, true, "更新过程发生错误: {}", e);
Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
Ok(false)
}
}
}
/// 根据profile name修改profiles
#[tauri::command]
pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String,
) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
let profiles = IProfiles {
current: Some(profile_index),
items: None,
};
patch_profiles_config(profiles).await
}
/// 修改某个profile item的
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
Ok(())
}
/// 查看配置文件
#[tauri::command]
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
let file = {
wrap_err!(Config::profiles().latest().get_item(&index))?
.file
.clone()
.ok_or("the file field is null")
}?;
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
if !path.exists() {
ret_err!("the file not found");
}
wrap_err!(help::open_file(app_handle, path))
}
/// 读取配置文件内容
#[tauri::command]
pub fn read_profile_file(index: String) -> CmdResult<String> {
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
Ok(data)
}

View File

@ -0,0 +1,24 @@
use super::CmdResult;
use crate::module::mihomo::MihomoManager;
#[tauri::command]
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
let mannager = MihomoManager::global();
mannager
.refresh_proxies()
.await
.map(|_| mannager.get_proxies())
.or_else(|_| Ok(mannager.get_proxies()))
}
#[tauri::command]
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
let mannager = MihomoManager::global();
mannager
.refresh_providers_proxies()
.await
.map(|_| mannager.get_providers_proxies())
.or_else(|_| Ok(mannager.get_providers_proxies()))
}

View File

@ -0,0 +1,36 @@
use super::CmdResult;
use crate::{config::*, wrap_err};
use anyhow::Context;
use serde_yaml::Mapping;
use std::collections::HashMap;
/// 获取运行时配置
#[tauri::command]
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
Ok(Config::runtime().latest().config.clone())
}
/// 获取运行时YAML配置
#[tauri::command]
pub fn get_runtime_yaml() -> CmdResult<String> {
let runtime = Config::runtime();
let runtime = runtime.latest();
let config = runtime.config.as_ref();
wrap_err!(config
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
.and_then(
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
))
}
/// 获取运行时存在的键
#[tauri::command]
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
Ok(Config::runtime().latest().exists_keys.clone())
}
/// 获取运行时日志
#[tauri::command]
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
Ok(Config::runtime().latest().chain_logs.clone())
}

View File

@ -0,0 +1,116 @@
use super::CmdResult;
use crate::{config::*, core::*, utils::dirs, wrap_err};
use std::fs;
/// 保存profiles的配置
#[tauri::command]
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
if file_data.is_none() {
return Ok(());
}
// 在异步操作前完成所有文件操作
let (file_path, original_content, is_merge_file) = {
let profiles = Config::profiles();
let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件
let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge");
let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
(profiles_dir.join(path), content, is_merge)
};
// 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string();
println!(
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
file_path_str, is_merge_file
);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file {
println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global()
.validate_config_file(&file_path_str, Some(true))
.await
{
Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置
if let Err(e) = CoreManager::global().update_config().await {
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
}
return Ok(());
}
Ok((false, error_msg)) => {
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 发送合并文件专用错误通知
let result = (false, error_msg.clone());
crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件");
return Ok(());
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
return Err(e.to_string());
}
}
}
// 非merge文件使用完整验证流程
match CoreManager::global()
.validate_config_file(&file_path_str, None)
.await
{
Ok((true, _)) => {
println!("[cmd配置save] 验证成功");
Ok(())
}
Ok((false, error_msg)) => {
println!("[cmd配置save] 验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js")
|| error_msg.contains("Script syntax error")
|| error_msg.contains("Script must contain a main function")
|| error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error")
|| error_msg.contains("Failed to read file:")
|| (!file_path_str.ends_with(".js") && !is_script_error)
{
// 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败发送通知");
let result = (false, error_msg.clone());
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件");
} else if is_script_error {
// 脚本错误使用专门的通知处理
println!("[cmd配置save] 脚本文件验证失败,发送通知");
let result = (false, error_msg.clone());
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
} else {
// 普通配置错误使用一般通知
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg);
}
Ok(())
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
Err(e.to_string())
}
}
}

View File

@ -0,0 +1,40 @@
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
}

View File

@ -0,0 +1,94 @@
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 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)
});
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
let sysinfo = PlatformSpecification::new_async().await;
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() {
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)
}
}

28
src-tauri/src/cmd/uwp.rs Normal file
View File

@ -0,0 +1,28 @@
use super::CmdResult;
/// Platform-specific implementation for UWP functionality
#[cfg(windows)]
mod platform {
use super::CmdResult;
use crate::{core::win_uwp, wrap_err};
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)
}
}
/// Stub implementation for non-Windows platforms
#[cfg(not(windows))]
mod platform {
use super::CmdResult;
pub async fn invoke_uwp_tool() -> CmdResult {
Ok(())
}
}
/// Command exposed to Tauri
#[tauri::command]
pub async fn invoke_uwp_tool() -> CmdResult {
platform::invoke_uwp_tool().await
}

View File

@ -0,0 +1,104 @@
use super::CmdResult;
use crate::core::*;
/// 发送脚本验证通知消息
#[tauri::command]
pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
handle::Handle::notice_message(&status, &msg);
Ok(())
}
/// 处理脚本验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read script file:") {
"config_validate::script_error"
} else if error_msg.starts_with("Script syntax error:") {
"config_validate::script_syntax_error"
} else if error_msg == "Script must contain a main function" {
"config_validate::script_missing_main"
} else {
// 如果是其他类型错误,作为一般脚本错误处理
"config_validate::script_error"
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}
/// 验证指定脚本文件
#[tauri::command]
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global()
.validate_config_file(&file_path, None)
.await
{
Ok(result) => {
handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值
}
Err(e) => {
let error_msg = e.to_string();
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
handle::Handle::notice_message("config_validate::process_terminated", &error_msg);
Ok(false)
}
}
}
/// 处理YAML验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
// 检查是否为merge文件
let is_merge_file = file_type.contains("合并");
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read file:") {
"config_validate::yaml_read_error"
} else if error_msg.starts_with("YAML syntax error:") {
if is_merge_file {
"config_validate::merge_syntax_error"
} else {
"config_validate::yaml_syntax_error"
}
} else if error_msg.contains("mapping values are not allowed") {
if is_merge_file {
"config_validate::merge_mapping_error"
} else {
"config_validate::yaml_mapping_error"
}
} else if error_msg.contains("did not find expected key") {
if is_merge_file {
"config_validate::merge_key_error"
} else {
"config_validate::yaml_key_error"
}
} else {
// 如果是其他类型错误,根据文件类型作为一般错误处理
if is_merge_file {
"config_validate::merge_error"
} else {
"config_validate::yaml_error"
}
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}

View File

@ -0,0 +1,16 @@
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))
}
/// 修改Verge配置
#[tauri::command]
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload, false).await)
}

View File

@ -0,0 +1,46 @@
use super::CmdResult;
use crate::{config::*, core, feat, wrap_err};
use reqwest_dav::list_cmd::ListFile;
/// 保存 WebDAV 配置
#[tauri::command]
pub async fn save_webdav_config(url: String, username: String, password: String) -> CmdResult<()> {
let patch = IVerge {
webdav_url: Some(url),
webdav_username: Some(username),
webdav_password: Some(password),
..IVerge::default()
};
Config::verge().draft().patch_config(patch.clone());
Config::verge().apply();
Config::verge()
.data()
.save_file()
.map_err(|err| err.to_string())?;
core::backup::WebDavClient::global().reset();
Ok(())
}
/// 创建 WebDAV 备份并上传
#[tauri::command]
pub async fn create_webdav_backup() -> CmdResult<()> {
wrap_err!(feat::create_backup_and_upload_webdav().await)
}
/// 列出 WebDAV 上的备份文件
#[tauri::command]
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
wrap_err!(feat::list_wevdav_backup().await)
}
/// 删除 WebDAV 上的备份文件
#[tauri::command]
pub async fn delete_webdav_backup(filename: String) -> CmdResult<()> {
wrap_err!(feat::delete_webdav_backup(filename).await)
}
/// 从 WebDAV 恢复备份文件
#[tauri::command]
pub async fn restore_webdav_backup(filename: String) -> CmdResult<()> {
wrap_err!(feat::restore_webdav_backup(filename).await)
}

View File

@ -1,698 +0,0 @@
use crate::{
config::*,
core::*,
feat,
utils::{dirs, help},
};
use crate::{log_err, ret_err, wrap_err};
use anyhow::{Context, Result};
use network_interface::NetworkInterface;
use serde_yaml::Mapping;
use std::collections::HashMap;
use sysproxy::{Autoproxy, Sysproxy};
type CmdResult<T = ()> = Result<T, String>;
use reqwest_dav::list_cmd::ListFile;
use tauri::Manager;
use std::fs;
#[tauri::command]
pub fn copy_clash_env() -> CmdResult {
feat::copy_clash_env();
Ok(())
}
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
let _ = tray::Tray::global().update_menu();
Ok(Config::profiles().data().clone())
}
#[tauri::command]
pub async fn enhance_profiles() -> CmdResult {
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[enhance_profiles] 配置更新成功");
log_err!(tray::Tray::global().update_tooltip());
handle::Handle::refresh_clash();
Ok(())
}
Ok((false, error_msg)) => {
println!("[enhance_profiles] 配置验证失败: {}", error_msg);
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(())
}
Err(e) => {
println!("[enhance_profiles] 更新过程发生错误: {}", e);
handle::Handle::notice_message("config_validate::process_terminated", &e.to_string());
Ok(())
}
}
}
#[tauri::command]
pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
}
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option).await)
}
#[tauri::command]
pub async fn delete_profile(index: String) -> CmdResult {
let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?;
if should_update {
wrap_err!(CoreManager::global().update_config().await)?;
handle::Handle::refresh_clash();
}
Ok(())
}
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(
profiles: IProfiles
) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().latest().current.clone();
println!("[cmd配置patch] 当前配置: {:?}", current_profile);
// 更新profiles配置
println!("[cmd配置patch] 正在更新配置草稿");
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
// 更新配置并进行验证
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[cmd配置patch] 配置更新成功");
handle::Handle::refresh_clash();
let _ = tray::Tray::global().update_tooltip();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(true)
}
Ok((false, error_msg)) => {
println!("[cmd配置patch] 配置验证失败: {}", error_msg);
Config::profiles().discard();
// 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile {
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
};
// 静默恢复,不触发验证
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
println!("[cmd配置patch] 成功恢复到之前的配置");
}
// 发送验证错误通知
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(false)
}
Err(e) => {
println!("[cmd配置patch] 更新过程发生错误: {}", e);
Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", &e.to_string());
Ok(false)
}
}
}
/// 根据profile name修改profiles
#[tauri::command]
pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String
) -> CmdResult<bool> {
let profiles = IProfiles{current: Some(profile_index), items: None};
patch_profiles_config(profiles).await
}
/// 修改某个profile item的
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
Ok(())
}
#[tauri::command]
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
let file = {
wrap_err!(Config::profiles().latest().get_item(&index))?
.file
.clone()
.ok_or("the file field is null")
}?;
let path = wrap_err!(dirs::app_profiles_dir())?.join(file);
if !path.exists() {
ret_err!("the file not found");
}
wrap_err!(help::open_file(app_handle, path))
}
#[tauri::command]
pub fn read_profile_file(index: String) -> CmdResult<String> {
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
Ok(data)
}
/// 保存profiles的配置
#[tauri::command]
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
if file_data.is_none() {
return Ok(());
}
// 在异步操作前完成所有文件操作
let (file_path, original_content, is_merge_file) = {
let profiles = Config::profiles();
let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件
let is_merge = item.itype.as_ref().map_or(false, |t| t == "merge");
let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
(profiles_dir.join(path), content, is_merge)
};
// 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string();
println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file {
println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await {
Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置
if let Err(e) = CoreManager::global().update_config().await {
println!("[cmd配置save] 更新整体配置时发生错误: {}", e);
log::warn!(target: "app", "更新整体配置时发生错误: {}", e);
}
return Ok(());
}
Ok((false, error_msg)) => {
println!("[cmd配置save] merge文件语法验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 发送合并文件专用错误通知
let result = (false, error_msg.clone());
handle_yaml_validation_notice(&result, "合并配置文件");
return Ok(());
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
return Err(e.to_string());
}
}
}
// 非merge文件使用完整验证流程
match CoreManager::global().validate_config_file(&file_path_str, None).await {
Ok((true, _)) => {
println!("[cmd配置save] 验证成功");
Ok(())
}
Ok((false, error_msg)) => {
println!("[cmd配置save] 验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js") ||
error_msg.contains("Script syntax error") ||
error_msg.contains("Script must contain a main function") ||
error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error") ||
error_msg.contains("Failed to read file:") ||
(!file_path_str.ends_with(".js") && !is_script_error) {
// 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败发送通知");
let result = (false, error_msg.clone());
handle_yaml_validation_notice(&result, "YAML配置文件");
} else if is_script_error {
// 脚本错误使用专门的通知处理
println!("[cmd配置save] 脚本文件验证失败,发送通知");
let result = (false, error_msg.clone());
handle_script_validation_notice(&result, "脚本文件");
} else {
// 普通配置错误使用一般通知
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg);
}
Ok(())
}
Err(e) => {
println!("[cmd配置save] 验证过程发生错误: {}", e);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
Err(e.to_string())
}
}
}
#[tauri::command]
pub fn get_clash_info() -> CmdResult<ClashInfo> {
Ok(Config::clash().latest().get_client_info())
}
#[tauri::command]
pub fn get_runtime_config() -> CmdResult<Option<Mapping>> {
Ok(Config::runtime().latest().config.clone())
}
#[tauri::command]
pub fn get_runtime_yaml() -> CmdResult<String> {
let runtime = Config::runtime();
let runtime = runtime.latest();
let config = runtime.config.as_ref();
wrap_err!(config
.ok_or(anyhow::anyhow!("failed to parse config to yaml file"))
.and_then(
|config| serde_yaml::to_string(config).context("failed to convert config to yaml")
))
}
#[tauri::command]
pub fn get_runtime_exists() -> CmdResult<Vec<String>> {
Ok(Config::runtime().latest().exists_keys.clone())
}
#[tauri::command]
pub fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
Ok(Config::runtime().latest().chain_logs.clone())
}
#[tauri::command]
pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
wrap_err!(feat::patch_clash(payload).await)
}
#[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult {
Ok(feat::change_clash_mode(payload))
}
#[tauri::command]
pub fn get_verge_config() -> CmdResult<IVergeResponse> {
let verge = Config::verge();
let verge_data = verge.data().clone();
Ok(IVergeResponse::from(verge_data))
}
#[tauri::command]
pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
wrap_err!(feat::patch_verge(payload, false).await)
}
#[tauri::command]
pub async fn change_clash_core(clash_core: String) -> CmdResult<Option<String>> {
log::info!(target: "app", "changing core to {clash_core}");
match CoreManager::global().change_core(Some(clash_core.clone())).await {
Ok(_) => {
log::info!(target: "app", "core changed to {clash_core}");
handle::Handle::notice_message("config_core::change_success", &clash_core);
handle::Handle::refresh_clash();
Ok(None)
}
Err(err) => {
let error_msg = err.to_string();
log::error!(target: "app", "failed to change core: {error_msg}");
handle::Handle::notice_message("config_core::change_error", &error_msg);
Ok(Some(error_msg))
}
}
}
/// restart the sidecar
#[tauri::command]
pub async fn restart_core() -> CmdResult {
wrap_err!(CoreManager::global().restart_core().await)
}
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Sysproxy::get_system_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert(
"server".into(),
format!("{}:{}", current.host, current.port).into(),
);
map.insert("bypass".into(), current.bypass.into());
Ok(map)
}
/// get the system proxy
#[tauri::command]
pub fn get_auto_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Autoproxy::get_auto_proxy())?;
let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into());
map.insert("url".into(), current.url.into());
Ok(map)
}
#[tauri::command]
pub fn open_app_dir() -> CmdResult<()> {
let app_dir = wrap_err!(dirs::app_home_dir())?;
wrap_err!(open::that(app_dir))
}
#[tauri::command]
pub fn open_core_dir() -> CmdResult<()> {
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
wrap_err!(open::that(core_dir))
}
#[tauri::command]
pub fn open_logs_dir() -> CmdResult<()> {
let log_dir = wrap_err!(dirs::app_logs_dir())?;
wrap_err!(open::that(log_dir))
}
#[tauri::command]
pub fn open_web_url(url: String) -> CmdResult<()> {
wrap_err!(open::that(url))
}
#[cfg(windows)]
pub mod uwp {
use super::*;
use crate::core::win_uwp;
#[tauri::command]
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)
}
}
#[tauri::command]
pub async fn clash_api_get_proxy_delay(
name: String,
url: Option<String>,
timeout: i32,
) -> CmdResult<clash_api::DelayRes> {
match clash_api::get_proxy_delay(name, url, timeout).await {
Ok(res) => Ok(res),
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
}
#[tauri::command]
pub async fn test_delay(url: String) -> CmdResult<u32> {
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
}
#[tauri::command]
pub fn get_app_dir() -> CmdResult<String> {
let app_home_dir = wrap_err!(dirs::app_home_dir())?
.to_string_lossy()
.to_string();
Ok(app_home_dir)
}
#[tauri::command]
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(name);
if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir);
}
if !icon_path.exists() {
let response = wrap_err!(reqwest::get(url).await)?;
let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
let content = wrap_err!(response.bytes().await)?;
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
Ok(icon_path.to_string_lossy().to_string())
}
#[tauri::command]
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
let file_path = std::path::Path::new(&path);
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
if !icon_dir.exists() {
let _ = std::fs::create_dir_all(&icon_dir);
}
let ext = match file_path.extension() {
Some(e) => e.to_string_lossy().to_string(),
None => "ico".to_string(),
};
let png_dest_path = icon_dir.join(format!("{name}.png"));
let ico_dest_path = icon_dir.join(format!("{name}.ico"));
let dest_path = icon_dir.join(format!("{name}.{ext}"));
if file_path.exists() {
std::fs::remove_file(png_dest_path).unwrap_or_default();
std::fs::remove_file(ico_dest_path).unwrap_or_default();
match std::fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),
}
} else {
Err("file not found".to_string())
}
}
#[tauri::command]
pub fn get_network_interfaces() -> Vec<String> {
use sysinfo::Networks;
let mut result = Vec::new();
let networks = Networks::new_with_refreshed_list();
for (interface_name, _) in &networks {
result.push(interface_name.clone());
}
result
}
#[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::NetworkInterface;
use network_interface::NetworkInterfaceConfig;
let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?;
let mut result = Vec::new();
for interface in interfaces {
if names.contains(&interface.name) {
result.push(interface);
}
}
Ok(result)
}
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_webview_window("main") {
if !window.is_devtools_open() {
window.open_devtools();
} else {
window.close_devtools();
}
}
}
#[tauri::command]
pub fn exit_app() {
feat::quit(Some(0));
}
#[tauri::command]
pub async fn save_webdav_config(url: String, username: String, password: String) -> CmdResult<()> {
let patch = IVerge {
webdav_url: Some(url),
webdav_username: Some(username),
webdav_password: Some(password),
..IVerge::default()
};
Config::verge().draft().patch_config(patch.clone());
Config::verge().apply();
Config::verge()
.data()
.save_file()
.map_err(|err| err.to_string())?;
backup::WebDavClient::global().reset();
Ok(())
}
#[tauri::command]
pub async fn create_webdav_backup() -> CmdResult<()> {
wrap_err!(feat::create_backup_and_upload_webdav().await)
}
#[tauri::command]
pub async fn list_webdav_backup() -> CmdResult<Vec<ListFile>> {
wrap_err!(feat::list_wevdav_backup().await)
}
#[tauri::command]
pub async fn delete_webdav_backup(filename: String) -> CmdResult<()> {
wrap_err!(feat::delete_webdav_backup(filename).await)
}
#[tauri::command]
pub async fn restore_webdav_backup(filename: String) -> CmdResult<()> {
wrap_err!(feat::restore_webdav_backup(filename).await)
}
#[tauri::command]
pub async fn restart_app() -> CmdResult<()> {
feat::restart_app();
Ok(())
}
#[cfg(not(windows))]
pub mod uwp {
use super::*;
#[tauri::command]
pub async fn invoke_uwp_tool() -> CmdResult {
Ok(())
}
}
#[tauri::command]
pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
handle::Handle::notice_message(&status, &msg);
Ok(())
}
/// 处理脚本验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read script file:") {
"config_validate::script_error"
} else if error_msg.starts_with("Script syntax error:") {
"config_validate::script_syntax_error"
} else if error_msg == "Script must contain a main function" {
"config_validate::script_missing_main"
} else {
// 如果是其他类型错误,作为一般脚本错误处理
"config_validate::script_error"
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}
/// 验证指定脚本文件
#[tauri::command]
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global().validate_config_file(&file_path, None).await {
Ok(result) => {
handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值
},
Err(e) => {
let error_msg = e.to_string();
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
handle::Handle::notice_message("config_validate::process_terminated", &error_msg);
Ok(false)
}
}
}
/// 处理YAML验证相关的所有消息通知
/// 统一通知接口,保持消息类型一致性
pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
// 检查是否为merge文件
let is_merge_file = file_type.contains("合并");
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
} else if error_msg.starts_with("Failed to read file:") {
"config_validate::yaml_read_error"
} else if error_msg.starts_with("YAML syntax error:") {
if is_merge_file {
"config_validate::merge_syntax_error"
} else {
"config_validate::yaml_syntax_error"
}
} else if error_msg.contains("mapping values are not allowed") {
if is_merge_file {
"config_validate::merge_mapping_error"
} else {
"config_validate::yaml_mapping_error"
}
} else if error_msg.contains("did not find expected key") {
if is_merge_file {
"config_validate::merge_key_error"
} else {
"config_validate::yaml_key_error"
}
} else {
// 如果是其他类型错误,根据文件类型作为一般错误处理
if is_merge_file {
"config_validate::merge_error"
} else {
"config_validate::yaml_error"
}
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg);
}
}

View File

@ -33,11 +33,12 @@ impl IClashTemp {
let mut map = Mapping::new();
let mut tun = Mapping::new();
tun.insert("enable".into(), false.into());
tun.insert("stack".into(), "mixed".into());
tun.insert("stack".into(), "gvisor".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")]

View File

@ -1,9 +1,9 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{
config::PrfItem,
enhance,
utils::{dirs, help},
core::{handle, CoreManager},
enhance, logging,
utils::{dirs, help, logging::Type},
};
use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell;
@ -66,32 +66,41 @@ impl Config {
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
Self::profiles().data().append_item(script_item.clone())?;
}
// 生成运行时配置
crate::log_err!(Self::generate().await);
if let Err(err) = Self::generate().await {
logging!(error, Type::Config, true, "生成运行时配置失败: {}", err);
} else {
logging!(info, Type::Config, true, "生成运行时配置成功");
}
// 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run);
let validation_result = if let Ok(_) = config_result {
let validation_result = if config_result.is_ok() {
// 验证配置文件
println!("[首次启动] 开始验证配置");
logging!(info, Type::Config, true, "开始验证配置");
match CoreManager::global().validate_config().await {
Ok((is_valid, error_msg)) => {
if !is_valid {
println!("[首次启动] 配置验证失败,使用默认最小配置启动: {}", error_msg);
logging!(
warn,
Type::Config,
true,
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
error_msg
);
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)
.await?;
Some(("config_validate::boot_error", error_msg))
} else {
println!("[首次启动] 配置验证成功");
logging!(info, Type::Config, true, "配置验证成功");
Some(("config_validate::success", String::new()))
}
}
Err(err) => {
println!("[首次启动] 验证进程执行失败: {}", err);
logging!(warn, Type::Config, true, "验证进程执行失败: {}", err);
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.await?;
@ -99,12 +108,9 @@ impl Config {
}
}
} else {
println!("[首次启动] 生成配置文件失败,使用默认配置");
logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置");
CoreManager::global()
.use_default_config(
"config_validate::error",
"",
)
.use_default_config("config_validate::error", "")
.await?;
Some(("config_validate::error", String::new()))
};

View File

@ -16,7 +16,7 @@ pub fn encrypt_data(data: &str) -> Result<String, Box<dyn std::error::Error>> {
// Generate random nonce
let mut nonce = vec![0u8; NONCE_LENGTH];
getrandom::getrandom(&mut nonce)?;
getrandom::fill(&mut nonce)?;
// Encrypt data
let ciphertext = cipher

View File

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

@ -234,10 +234,10 @@ impl PrfItem {
option: Option<PrfOption>,
) -> Result<PrfItem> {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let 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 accept_invalid_certs =
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
opt_ref.is_some_and(|o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone());

View File

@ -472,15 +472,17 @@ impl IProfiles {
/// 获取所有的profiles(uid名称)
pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> {
match self.items.as_ref() {
Some(items) => Some(items.iter().filter_map(|e| {
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()),
None => None,
}
})
.collect()
})
}
}

View File

@ -1,7 +1,7 @@
use crate::config::DEFAULT_PAC;
use crate::config::{deserialize_encrypted, serialize_encrypted};
use crate::utils::i18n;
use crate::utils::{dirs, help};
use crate::{
config::{deserialize_encrypted, serialize_encrypted, DEFAULT_PAC},
utils::{dirs, help, i18n},
};
use anyhow::Result;
use log::LevelFilter;
use serde::{Deserialize, Serialize};
@ -70,6 +70,9 @@ pub struct IVerge {
/// enable proxy guard
pub enable_proxy_guard: Option<bool>,
/// enable dns settings - this controls whether dns_config.yaml is applied
pub enable_dns_settings: Option<bool>,
/// always use default bypass
pub use_default_bypass: Option<bool>,
@ -102,6 +105,10 @@ pub struct IVerge {
/// enable global hotkey
pub enable_global_hotkey: Option<bool>,
/// 首页卡片设置
/// 控制首页各个卡片的显示和隐藏
pub home_cards: Option<serde_json::Value>,
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
@ -182,8 +189,16 @@ pub struct IVerge {
pub enable_tray_speed: Option<bool>,
/// 轻量模式 - 只保留内核运行
pub enable_lite_mode: Option<bool>,
pub enable_tray_icon: Option<bool>,
/// 自动进入轻量模式
pub enable_auto_light_weight_mode: Option<bool>,
/// 自动进入轻量模式的延迟(分钟)
pub auto_light_weight_minutes: Option<u64>,
/// 服务状态跟踪
pub service_state: Option<crate::core::service::ServiceState>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -245,7 +260,7 @@ impl IVerge {
env_type: Some("bash".into()),
#[cfg(target_os = "windows")]
env_type: Some("powershell".into()),
start_page: Some("/".into()),
start_page: Some("/home".into()),
traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_group_icon: Some(true),
@ -285,8 +300,13 @@ impl IVerge {
webdav_username: None,
webdav_password: None,
enable_tray_speed: Some(true),
enable_tray_icon: Some(true),
enable_global_hotkey: Some(true),
enable_lite_mode: Some(false),
enable_auto_light_weight_mode: Some(false),
auto_light_weight_minutes: Some(10),
enable_dns_settings: Some(true),
home_cards: None,
service_state: None,
..Self::default()
}
}
@ -368,7 +388,12 @@ impl IVerge {
patch!(webdav_username);
patch!(webdav_password);
patch!(enable_tray_speed);
patch!(enable_lite_mode);
patch!(enable_tray_icon);
patch!(enable_auto_light_weight_mode);
patch!(auto_light_weight_minutes);
patch!(enable_dns_settings);
patch!(home_cards);
patch!(service_state);
}
/// 在初始化前尝试拿到单例端口的值
@ -457,7 +482,12 @@ pub struct IVergeResponse {
pub webdav_username: Option<String>,
pub webdav_password: Option<String>,
pub enable_tray_speed: Option<bool>,
pub enable_lite_mode: 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_dns_settings: Option<bool>,
pub home_cards: Option<serde_json::Value>,
pub service_state: Option<crate::core::service::ServiceState>,
}
impl From<IVerge> for IVergeResponse {
@ -520,7 +550,12 @@ impl From<IVerge> for IVergeResponse {
webdav_username: verge.webdav_username,
webdav_password: verge.webdav_password,
enable_tray_speed: verge.enable_tray_speed,
enable_lite_mode: verge.enable_lite_mode,
enable_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_dns_settings: verge.enable_dns_settings,
home_cards: verge.home_cards,
service_state: verge.service_state,
}
}
}

View File

@ -1,16 +1,17 @@
use crate::config::Config;
use crate::utils::dirs;
use crate::{config::Config, utils::dirs};
use anyhow::Error;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use reqwest_dav::list_cmd::{ListEntity, ListFile};
use std::collections::HashMap;
use std::env::{consts::OS, temp_dir};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use std::{
collections::HashMap,
env::{consts::OS, temp_dir},
fs,
io::Write,
path::PathBuf,
sync::Arc,
time::Duration,
};
use tokio::time::timeout;
use zip::write::SimpleFileOptions;

View File

@ -1,156 +0,0 @@
use crate::config::Config;
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::collections::HashMap;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Rate {
pub up: u64,
pub down: u64,
}
/// PUT /configs
/// path 是绝对路径
pub async fn put_configs(path: &str) -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs?force=true");
let mut data = HashMap::new();
data.insert("path", path);
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client.put(&url).headers(headers).json(&data);
let response = builder.send().await?;
match response.status().as_u16() {
204 => Ok(()),
status => {
let body = response.text().await?;
// print!("failed to put configs with status \"{}\"\n{}\n{}", status, url, body);
bail!("failed to put configs with status \"{status}\"\n{url}\n{body}");
}
}
}
/// PATCH /configs
pub async fn patch_configs(config: &Mapping) -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs");
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client.patch(&url).headers(headers.clone()).json(config);
builder.send().await?;
Ok(())
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct DelayRes {
delay: u64,
}
/// GET /proxies/{name}/delay
/// 获取代理延迟
pub async fn get_proxy_delay(
name: String,
test_url: Option<String>,
timeout: i32,
) -> Result<DelayRes> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/proxies/{name}/delay");
let default_url = "http://cp.cloudflare.com/generate_204";
let test_url = test_url
.map(|s| if s.is_empty() { default_url.into() } else { s })
.unwrap_or(default_url.into());
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client
.get(&url)
.headers(headers)
.query(&[("timeout", &format!("{timeout}")), ("url", &test_url)]);
let response = builder.send().await?;
Ok(response.json::<DelayRes>().await?)
}
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
let client = { Config::clash().data().get_client_info() };
let server = format!("http://{}", client.server);
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
if let Some(secret) = client.secret {
let secret = format!("Bearer {}", secret).parse()?;
headers.insert("Authorization", secret);
}
Ok((server, headers))
}
/// 缩短clash的日志
#[allow(dead_code)]
pub fn parse_log(log: String) -> String {
if log.starts_with("time=") && log.len() > 33 {
return (log[33..]).to_owned();
}
if log.len() > 9 {
return (log[9..]).to_owned();
}
log
}
#[allow(dead_code)]
pub fn parse_check_output(log: String) -> String {
let t = log.find("time=");
let m = log.find("msg=");
let mr = log.rfind('"');
if let (Some(_), Some(m), Some(mr)) = (t, m, mr) {
let e = match log.find("level=error msg=") {
Some(e) => e + 17,
None => m + 5,
};
if mr > m {
return (log[e..mr]).to_owned();
}
}
let l = log.find("error=");
let r = log.find("path=").or(Some(log.len()));
if let (Some(l), Some(r)) = (l, r) {
return (log[(l + 6)..(r - 1)]).to_owned();
}
log
}
#[cfg(target_os = "macos")]
pub fn get_traffic_ws_url() -> Result<String> {
let (url, _) = clash_client_info()?;
let ws_url = url.replace("http://", "ws://") + "/traffic";
Ok(ws_url)
}
#[test]
fn test_parse_check_output() {
let str1 = r#"xxxx\n time="2022-11-18T20:42:58+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'""#;
//let str2 = r#"20:43:49 ERR [Config] configuration file test failed error=proxy 0: unsupport proxy type: hysteria path=xxx"#;
let str3 = r#"
"time="2022-11-18T21:38:01+08:00" level=info msg="Start initial configuration in progress"
time="2022-11-18T21:38:01+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'"
configuration file xxx\n
"#;
let res1 = parse_check_output(str1.into());
// let res2 = parse_check_output(str2.into());
let res3 = parse_check_output(str3.into());
assert_eq!(res1, res3);
}

View File

@ -1,96 +1,135 @@
use crate::config::*;
use crate::core::{clash_api, handle, service};
#[cfg(target_os = "macos")]
use crate::core::tray::Tray;
use crate::log_err;
use crate::utils::{dirs, help};
use anyhow::{bail, Result};
use crate::{
config::*,
core::{
handle,
service::{self},
},
logging, logging_error,
module::mihomo::MihomoManager,
utils::{
dirs,
help::{self},
logging::Type,
},
};
use anyhow::Result;
use once_cell::sync::OnceCell;
use serde_yaml::Mapping;
use std::{sync::Arc, time::Duration};
use tauri_plugin_shell::ShellExt;
use std::{fmt, path::PathBuf, sync::Arc};
use tauri_plugin_shell::{process::CommandChild, ShellExt};
use tokio::sync::Mutex;
use tokio::time::sleep;
#[derive(Debug)]
pub struct CoreManager {
running: Arc<Mutex<bool>>,
running: Arc<Mutex<RunningMode>>,
child_sidecar: Arc<Mutex<Option<CommandChild>>>,
}
/// 内核运行模式
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub enum RunningMode {
/// 服务模式运行
Service,
/// Sidecar 模式运行
Sidecar,
/// 未运行
NotRunning,
}
impl fmt::Display for RunningMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RunningMode::Service => write!(f, "Service"),
RunningMode::Sidecar => write!(f, "Sidecar"),
RunningMode::NotRunning => write!(f, "NotRunning"),
}
}
}
const CLASH_CORES: [&str; 2] = ["verge-mihomo", "verge-mihomo-alpha"];
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
CORE_MANAGER.get_or_init(|| CoreManager {
running: Arc::new(Mutex::new(false)),
})
/// 检查文件是否为脚本文件
fn is_script_file(&self, path: &str) -> Result<bool> {
// 1. 先通过扩展名快速判断
if path.ends_with(".yaml") || path.ends_with(".yml") {
return Ok(false); // YAML文件不是脚本文件
} else if path.ends_with(".js") {
return Ok(true); // JS文件是脚本文件
}
pub async fn init(&self) -> Result<()> {
log::trace!("run core start");
// 启动clash
log_err!(Self::global().start_core().await);
log::trace!("run core end");
Ok(())
// 2. 读取文件内容
let content = match std::fs::read_to_string(path) {
Ok(content) => content,
Err(err) => {
logging!(
warn,
Type::Config,
true,
"无法读取文件以检测类型: {}, 错误: {}",
path,
err
);
return Err(anyhow::anyhow!(
"Failed to read file to detect type: {}",
err
));
}
};
// 3. 检查是否存在明显的YAML特征
let has_yaml_features = content.contains(": ")
|| content.contains("#")
|| content.contains("---")
|| content.lines().any(|line| line.trim().starts_with("- "));
// 4. 检查是否存在明显的JS特征
let has_js_features = content.contains("function ")
|| content.contains("const ")
|| content.contains("let ")
|| content.contains("var ")
|| content.contains("//")
|| content.contains("/*")
|| content.contains("*/")
|| content.contains("export ")
|| content.contains("import ");
// 5. 决策逻辑
if has_yaml_features && !has_js_features {
// 只有YAML特征没有JS特征
return Ok(false);
} else if has_js_features && !has_yaml_features {
// 只有JS特征没有YAML特征
return Ok(true);
} else if has_yaml_features && has_js_features {
// 两种特征都有,需要更精细判断
// 优先检查是否有明确的JS结构特征
if content.contains("function main")
|| content.contains("module.exports")
|| content.contains("export default")
{
return Ok(true);
}
/// 停止核心运行
pub async fn stop_core(&self) -> Result<()> {
let mut running = self.running.lock().await;
// 检查冒号后是否有空格YAML的典型特征
let yaml_pattern_count = content.lines().filter(|line| line.contains(": ")).count();
if !*running {
log::debug!("core is not running");
return Ok(());
if yaml_pattern_count > 2 {
return Ok(false); // 多个键值对格式更可能是YAML
}
}
// 关闭tun模式
let mut disable = Mapping::new();
let mut tun = Mapping::new();
tun.insert("enable".into(), false.into());
disable.insert("tun".into(), tun.into());
log::debug!(target: "app", "disable tun mode");
log_err!(clash_api::patch_configs(&disable).await);
// 服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "stop the core by service");
service::stop_core_by_service().await?;
// 默认情况:无法确定时,假设为非脚本文件(更安全)
logging!(
debug,
Type::Config,
true,
"无法确定文件类型默认当作YAML处理: {}",
path
);
Ok(false)
}
*running = false;
Ok(())
}
/// 启动核心
pub async fn start_core(&self) -> Result<()> {
let mut running = self.running.lock().await;
if *running {
log::info!("core is running");
return Ok(());
}
let config_path = Config::generate_file(ConfigType::Run)?;
// 服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "try to run core in service mode");
service::run_core_by_service(&config_path).await?;
}
// 流量订阅
#[cfg(target_os = "macos")]
log_err!(Tray::global().subscribe_traffic().await);
*running = true;
Ok(())
}
/// 重启内核
pub async fn restart_core(&self) -> Result<()> {
// 重新启动app
self.stop_core().await?;
self.start_core().await?;
Ok(())
}
/// 使用默认配置
pub async fn use_default_config(&self, msg_type: &str, msg_content: &str) -> Result<()> {
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
@ -107,140 +146,25 @@ impl CoreManager {
handle::Handle::notice_message(msg_type, msg_content);
Ok(())
}
/// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
const CLASH_CORES: [&str; 2] = ["verge-mihomo", "verge-mihomo-alpha"];
if !CLASH_CORES.contains(&clash_core.as_str()) {
bail!("invalid clash core name \"{clash_core}\"");
}
log::info!(target: "app", "change core to `{clash_core}`");
// 1. 先更新内核配置(但不应用)
Config::verge().draft().clash_core = Some(clash_core);
// 2. 使用新内核验证配置
println!("[切换内核] 使用新内核验证配置");
match self.validate_config().await {
Ok((true, _)) => {
println!("[切换内核] 配置验证通过,开始切换内核");
// 3. 验证通过后,应用内核配置并重启
Config::verge().apply();
log_err!(Config::verge().latest().save_file());
match self.restart_core().await {
Ok(_) => {
println!("[切换内核] 内核切换成功");
Config::runtime().apply();
Ok(())
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
Config::verge().discard();
Config::runtime().discard();
Err(err)
}
}
}
Ok((false, error_msg)) => {
println!("[切换内核] 配置验证失败: {}", error_msg);
// 使用默认配置并继续切换内核
self.use_default_config("config_validate::core_change", &error_msg).await?;
Config::verge().apply();
log_err!(Config::verge().latest().save_file());
match self.restart_core().await {
Ok(_) => {
println!("[切换内核] 内核切换成功(使用默认配置)");
Ok(())
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
Config::verge().discard();
Err(err)
}
}
}
Err(err) => {
println!("[切换内核] 验证过程发生错误: {}", err);
Config::verge().discard();
Err(err)
}
}
}
/// 内部验证配置文件的实现
async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> {
println!("[core配置验证] 开始验证配置文件: {}", config_path);
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
println!("[core配置验证] 使用内核: {}", clash_core);
let app_handle = handle::Handle::global().app_handle().unwrap();
let test_dir = dirs::app_home_dir()?.join("test");
let test_dir = dirs::path_to_str(&test_dir)?;
println!("[core配置验证] 测试目录: {}", test_dir);
// 使用子进程运行clash验证配置
println!("[core配置验证] 运行子进程验证配置");
let output = app_handle
.shell()
.sidecar(clash_core)?
.args(["-t", "-d", test_dir, "-f", config_path])
.output()
.await?;
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
// 检查进程退出状态和错误输出
let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"];
let has_error = !output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
println!("\n[core配置验证] -------- 验证结果 --------");
println!("[core配置验证] 进程退出状态: {:?}", output.status);
if !stderr.is_empty() {
println!("[core配置验证] stderr输出:\n{}", stderr);
}
if !stdout.is_empty() {
println!("[core配置验证] stdout输出:\n{}", stdout);
}
if has_error {
println!("[core配置验证] 发现错误,开始处理错误信息");
let error_msg = if !stdout.is_empty() {
stdout.to_string()
} else if !stderr.is_empty() {
stderr.to_string()
} else if let Some(code) = output.status.code() {
format!("验证进程异常退出,退出码: {}", code)
} else {
"验证进程被终止".to_string()
};
println!("[core配置验证] -------- 验证结束 --------\n");
Ok((false, error_msg)) // 返回错误消息给调用者处理
} else {
println!("[core配置验证] 验证成功");
println!("[core配置验证] -------- 验证结束 --------\n");
Ok((true, String::new()))
}
}
/// 验证运行时配置
pub async fn validate_config(&self) -> Result<(bool, String)> {
logging!(info, Type::Config, true, "生成临时配置文件用于验证");
let config_path = Config::generate_file(ConfigType::Check)?;
let config_path = dirs::path_to_str(&config_path)?;
self.validate_config_internal(config_path).await
}
/// 验证指定的配置文件
pub async fn validate_config_file(&self, config_path: &str, is_merge_file: Option<bool>) -> Result<(bool, String)> {
pub async fn validate_config_file(
&self,
config_path: &str,
is_merge_file: Option<bool>,
) -> Result<(bool, String)> {
// 检查程序是否正在退出,如果是则跳过验证
if handle::Handle::global().is_exiting() {
logging!(info, Type::Core, true, "应用正在退出,跳过验证");
return Ok((true, String::new()));
}
// 检查文件是否存在
if !std::path::Path::new(config_path).exists() {
let error_msg = format!("File not found: {}", config_path);
@ -250,7 +174,13 @@ impl CoreManager {
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
if is_merge_file.unwrap_or(false) {
println!("[core配置验证] 检测到Merge文件仅进行语法检查: {}", config_path);
logging!(
info,
Type::Config,
true,
"检测到Merge文件仅进行语法检查: {}",
config_path
);
return self.validate_file_syntax(config_path).await;
}
@ -262,88 +192,135 @@ impl CoreManager {
Ok(result) => result,
Err(err) => {
// 如果无法确定文件类型尝试使用Clash内核验证
log::warn!(target: "app", "无法确定文件类型: {}, 错误: {}", config_path, err);
logging!(
warn,
Type::Config,
true,
"无法确定文件类型: {}, 错误: {}",
config_path,
err
);
return self.validate_config_internal(config_path).await;
}
}
};
if is_script {
log::info!(target: "app", "检测到脚本文件使用JavaScript验证: {}", config_path);
logging!(
info,
Type::Config,
true,
"检测到脚本文件使用JavaScript验证: {}",
config_path
);
return self.validate_script_file(config_path).await;
}
// 对YAML配置文件使用Clash内核验证
log::info!(target: "app", "使用Clash内核验证配置文件: {}", config_path);
logging!(
info,
Type::Config,
true,
"使用Clash内核验证配置文件: {}",
config_path
);
self.validate_config_internal(config_path).await
}
/// 检查文件是否为脚本文件
fn is_script_file(&self, path: &str) -> Result<bool> {
// 1. 先通过扩展名快速判断
if path.ends_with(".yaml") || path.ends_with(".yml") {
return Ok(false); // YAML文件不是脚本文件
} else if path.ends_with(".js") {
return Ok(true); // JS文件是脚本文件
/// 内部验证配置文件的实现
async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> {
// 检查程序是否正在退出,如果是则跳过验证
if handle::Handle::global().is_exiting() {
logging!(info, Type::Core, true, "应用正在退出,跳过验证");
return Ok((true, String::new()));
}
// 2. 读取文件内容
let content = match std::fs::read_to_string(path) {
Ok(content) => content,
Err(err) => {
log::warn!(target: "app", "无法读取文件以检测类型: {}, 错误: {}", path, err);
return Err(anyhow::anyhow!("Failed to read file to detect type: {}", err));
logging!(
info,
Type::Config,
true,
"开始验证配置文件: {}",
config_path
);
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
logging!(info, Type::Config, true, "使用内核: {}", clash_core);
let app_handle = handle::Handle::global().app_handle().unwrap();
let app_dir = dirs::app_home_dir()?;
let app_dir_str = dirs::path_to_str(&app_dir)?;
logging!(info, Type::Config, true, "验证目录: {}", app_dir_str);
// 使用子进程运行clash验证配置
let output = app_handle
.shell()
.sidecar(clash_core)?
.args(["-t", "-d", app_dir_str, "-f", config_path])
.output()
.await?;
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
// 检查进程退出状态和错误输出
let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"];
let has_error =
!output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
logging!(info, Type::Config, true, "-------- 验证结果 --------");
if !stderr.is_empty() {
logging!(info, Type::Config, true, "stderr输出:\n{}", stderr);
}
if has_error {
logging!(info, Type::Config, true, "发现错误,开始处理错误信息");
let error_msg = if !stdout.is_empty() {
stdout.to_string()
} else if !stderr.is_empty() {
stderr.to_string()
} else if let Some(code) = output.status.code() {
format!("验证进程异常退出,退出码: {}", code)
} else {
"验证进程被终止".to_string()
};
// 3. 检查是否存在明显的YAML特征
let has_yaml_features = content.contains(": ") ||
content.contains("#") ||
content.contains("---") ||
content.lines().any(|line| line.trim().starts_with("- "));
// 4. 检查是否存在明显的JS特征
let has_js_features = content.contains("function ") ||
content.contains("const ") ||
content.contains("let ") ||
content.contains("var ") ||
content.contains("//") ||
content.contains("/*") ||
content.contains("*/") ||
content.contains("export ") ||
content.contains("import ");
// 5. 决策逻辑
if has_yaml_features && !has_js_features {
// 只有YAML特征没有JS特征
return Ok(false);
} else if has_js_features && !has_yaml_features {
// 只有JS特征没有YAML特征
return Ok(true);
} else if has_yaml_features && has_js_features {
// 两种特征都有,需要更精细判断
// 优先检查是否有明确的JS结构特征
if content.contains("function main") ||
content.contains("module.exports") ||
content.contains("export default") {
return Ok(true);
}
// 检查冒号后是否有空格YAML的典型特征
let yaml_pattern_count = content.lines()
.filter(|line| line.contains(": "))
.count();
if yaml_pattern_count > 2 {
return Ok(false); // 多个键值对格式更可能是YAML
logging!(info, Type::Config, true, "-------- 验证结束 --------");
Ok((false, error_msg)) // 返回错误消息给调用者处理
} else {
logging!(info, Type::Config, true, "验证成功");
logging!(info, Type::Config, true, "-------- 验证结束 --------");
Ok((true, String::new()))
}
}
/// 只进行文件语法检查,不进行完整验证
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
logging!(info, Type::Config, true, "开始检查文件: {}", config_path);
// 默认情况:无法确定时,假设为非脚本文件(更安全)
log::debug!(target: "app", "无法确定文件类型默认当作YAML处理: {}", path);
Ok(false)
// 读取文件内容
let content = match std::fs::read_to_string(config_path) {
Ok(content) => content,
Err(err) => {
let error_msg = format!("Failed to read file: {}", err);
logging!(error, Type::Config, true, "无法读取文件: {}", error_msg);
return Ok((false, error_msg));
}
};
// 对YAML文件尝试解析只检查语法正确性
logging!(info, Type::Config, true, "进行YAML语法检查");
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
Ok(_) => {
logging!(info, Type::Config, true, "YAML语法检查通过");
Ok((true, String::new()))
}
Err(err) => {
// 使用标准化的前缀,以便错误处理函数能正确识别
let error_msg = format!("YAML syntax error: {}", err);
logging!(error, Type::Config, true, "YAML语法错误: {}", error_msg);
Ok((false, error_msg))
}
}
}
/// 验证脚本文件语法
async fn validate_script_file(&self, path: &str) -> Result<(bool, String)> {
// 读取脚本内容
@ -351,13 +328,13 @@ impl CoreManager {
Ok(content) => content,
Err(err) => {
let error_msg = format!("Failed to read script file: {}", err);
log::warn!(target: "app", "脚本语法错误: {}", err);
logging!(warn, Type::Config, true, "脚本语法错误: {}", err);
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
return Ok((false, error_msg));
}
};
log::debug!(target: "app", "验证脚本文件: {}", path);
logging!(debug, Type::Config, true, "验证脚本文件: {}", path);
// 使用boa引擎进行基本语法检查
use boa_engine::{Context, Source};
@ -367,114 +344,333 @@ impl CoreManager {
match result {
Ok(_) => {
log::debug!(target: "app", "脚本语法验证通过: {}", path);
logging!(debug, Type::Config, true, "脚本语法验证通过: {}", path);
// 检查脚本是否包含main函数
if !content.contains("function main") && !content.contains("const main") && !content.contains("let main") {
if !content.contains("function main")
&& !content.contains("const main")
&& !content.contains("let main")
{
let error_msg = "Script must contain a main function";
log::warn!(target: "app", "脚本缺少main函数: {}", path);
logging!(warn, Type::Config, true, "脚本缺少main函数: {}", path);
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
return Ok((false, error_msg.to_string()));
}
Ok((true, String::new()))
},
}
Err(err) => {
let error_msg = format!("Script syntax error: {}", err);
log::warn!(target: "app", "脚本语法错误: {}", err);
logging!(warn, Type::Config, true, "脚本语法错误: {}", err);
//handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg);
Ok((false, error_msg))
}
}
}
/// 更新proxies等配置
pub async fn update_config(&self) -> Result<(bool, String)> {
println!("[core配置更新] 开始更新配置");
// 1. 先生成新的配置内容
println!("[core配置更新] 生成新的配置内容");
Config::generate().await?;
// 2. 生成临时文件并进行验证
println!("[core配置更新] 生成临时配置文件用于验证");
let temp_config = Config::generate_file(ConfigType::Check)?;
let temp_config = dirs::path_to_str(&temp_config)?;
println!("[core配置更新] 临时配置文件路径: {}", temp_config);
// 3. 验证配置
match self.validate_config().await {
Ok((true, _)) => {
println!("[core配置更新] 配置验证通过");
// 4. 验证通过后,生成正式的运行时配置
println!("[core配置更新] 生成运行时配置");
let run_path = Config::generate_file(ConfigType::Run)?;
let run_path = dirs::path_to_str(&run_path)?;
// 5. 应用新配置
println!("[core配置更新] 应用新配置");
for i in 0..3 {
match clash_api::put_configs(run_path).await {
Ok(_) => {
println!("[core配置更新] 配置应用成功");
Config::runtime().apply();
// 检查程序是否正在退出,如果是则跳过完整验证流程
if handle::Handle::global().is_exiting() {
logging!(info, Type::Config, true, "应用正在退出,跳过验证");
return Ok((true, String::new()));
}
Err(err) => {
if i < 2 {
println!("[core配置更新] 第{}次重试应用配置", i + 1);
log::info!(target: "app", "{err}");
sleep(Duration::from_millis(100)).await;
} else {
println!("[core配置更新] 配置应用失败: {}", err);
Config::runtime().discard();
return Ok((false, err.to_string()));
}
}
}
}
Ok((true, String::new()))
logging!(info, Type::Config, true, "开始更新配置");
// 1. 先生成新的配置内容
logging!(info, Type::Config, true, "生成新的配置内容");
Config::generate().await?;
// 2. 验证配置
match self.validate_config().await {
Ok((true, _)) => {
logging!(info, Type::Config, true, "配置验证通过");
// 4. 验证通过后,生成正式的运行时配置
logging!(info, Type::Config, true, "生成运行时配置");
let run_path = Config::generate_file(ConfigType::Run)?;
logging_error!(Type::Config, true, self.put_configs_force(run_path).await);
Ok((true, "something".into()))
}
Ok((false, error_msg)) => {
println!("[core配置更新] 配置验证失败: {}", error_msg);
logging!(warn, Type::Config, true, "配置验证失败: {}", error_msg);
Config::runtime().discard();
Ok((false, error_msg))
}
Err(e) => {
println!("[core配置更新] 验证过程发生错误: {}", e);
logging!(warn, Type::Config, true, "验证过程发生错误: {}", e);
Config::runtime().discard();
Err(e)
}
}
}
/// 只进行文件语法检查,不进行完整验证
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
println!("[core配置语法检查] 开始检查文件: {}", config_path);
// 读取文件内容
let content = match std::fs::read_to_string(config_path) {
Ok(content) => content,
Err(err) => {
let error_msg = format!("Failed to read file: {}", err);
println!("[core配置语法检查] 无法读取文件: {}", error_msg);
return Ok((false, error_msg));
}
};
// 对YAML文件尝试解析只检查语法正确性
println!("[core配置语法检查] 进行YAML语法检查");
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
pub async fn put_configs_force(&self, path_buf: PathBuf) -> Result<(), String> {
let run_path_str = dirs::path_to_str(&path_buf).map_err(|e| {
let msg = e.to_string();
logging_error!(Type::Core, true, "{}", msg);
msg
});
match MihomoManager::global()
.put_configs_force(run_path_str?)
.await
{
Ok(_) => {
println!("[core配置语法检查] YAML语法检查通过");
Ok((true, String::new()))
},
Err(err) => {
// 使用标准化的前缀,以便错误处理函数能正确识别
let error_msg = format!("YAML syntax error: {}", err);
println!("[core配置语法检查] YAML语法错误: {}", error_msg);
Ok((false, error_msg))
Config::runtime().apply();
logging!(info, Type::Core, true, "Configuration updated successfully");
Ok(())
}
Err(e) => {
let msg = e.to_string();
Config::runtime().discard();
logging_error!(Type::Core, true, "Failed to update configuration: {}", msg);
Err(msg)
}
}
}
}
impl CoreManager {
async fn start_core_by_sidecar(&self) -> Result<()> {
logging!(trace, Type::Core, true, "Running core by sidecar");
let config_file = &Config::generate_file(ConfigType::Run)?;
let app_handle = handle::Handle::global()
.app_handle()
.ok_or(anyhow::anyhow!("failed to get app handle"))?;
let clash_core = Config::verge()
.latest()
.clash_core
.clone()
.unwrap_or("verge-mihomo".to_string());
let config_dir = dirs::app_home_dir()?;
let (_, child) = app_handle
.shell()
.sidecar(&clash_core)?
.args([
"-d",
dirs::path_to_str(&config_dir)?,
"-f",
dirs::path_to_str(config_file)?,
])
.spawn()?;
let pid = child.pid();
logging!(
trace,
Type::Core,
true,
"Started core by sidecar pid: {}",
pid
);
*self.child_sidecar.lock().await = Some(child);
self.set_running_mode(RunningMode::Sidecar).await;
Ok(())
}
async fn stop_core_by_sidecar(&self) -> Result<()> {
logging!(trace, Type::Core, true, "Stopping core by sidecar");
if let Some(child) = self.child_sidecar.lock().await.take() {
let pid = child.pid();
child.kill()?;
logging!(
trace,
Type::Core,
true,
"Stopped core by sidecar pid: {}",
pid
);
}
self.set_running_mode(RunningMode::NotRunning).await;
Ok(())
}
}
impl CoreManager {
async fn start_core_by_service(&self) -> Result<()> {
logging!(trace, Type::Core, true, "Running core by service");
let config_file = &Config::generate_file(ConfigType::Run)?;
service::run_core_by_service(config_file).await?;
self.set_running_mode(RunningMode::Service).await;
Ok(())
}
async fn stop_core_by_service(&self) -> Result<()> {
logging!(trace, Type::Core, true, "Stopping core by service");
service::stop_core_by_service().await?;
self.set_running_mode(RunningMode::NotRunning).await;
Ok(())
}
}
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
CORE_MANAGER.get_or_init(|| CoreManager {
running: Arc::new(Mutex::new(RunningMode::NotRunning)),
child_sidecar: Arc::new(Mutex::new(None)),
})
}
pub async fn init(&self) -> Result<()> {
logging!(trace, Type::Core, "Initializing core");
if service::is_service_available().await.is_ok() {
logging!(info, Type::Core, true, "服务可用,直接使用服务模式");
// 检查版本是否需要重装
if service::check_service_needs_reinstall().await {
logging!(info, Type::Core, true, "服务版本不匹配,执行重装");
service::reinstall_service().await?;
}
self.start_core_by_service().await?;
} else {
// 服务不可用,获取服务状态
let service_state = service::ServiceState::get();
let has_service_install_record = service_state.last_install_time > 0;
if service_state.prefer_sidecar {
logging!(
info,
Type::Core,
true,
"用户偏好Sidecar模式使用Sidecar模式启动"
);
self.start_core_by_sidecar().await?;
}
// 检查是否已经有服务安装记录,如果没有,则尝试安装
else if !has_service_install_record {
logging!(info, Type::Core, true, "首次运行,服务不可用,尝试安装");
match service::install_service().await {
Ok(_) => {
logging!(info, Type::Core, true, "服务安装成功");
let mut new_state = service::ServiceState::default();
new_state.record_install();
new_state.prefer_sidecar = false;
new_state.save()?;
if service::is_service_available().await.is_ok() {
self.start_core_by_service().await?;
logging!(info, Type::Core, true, "服务启动成功");
} else {
logging!(
warn,
Type::Core,
true,
"服务安装成功但未能连接回退到Sidecar模式"
);
self.start_core_by_sidecar().await?;
}
}
Err(err) => {
// 安装失败记录错误并使用sidecar模式
logging!(warn, Type::Core, true, "服务安装失败: {}", err);
let new_state = service::ServiceState {
last_error: Some(err.to_string()),
prefer_sidecar: true,
..Default::default()
};
new_state.save()?;
self.start_core_by_sidecar().await?;
}
}
} else {
logging!(
info,
Type::Core,
true,
"有服务安装记录但服务不可用使用Sidecar模式"
);
self.start_core_by_sidecar().await?;
}
}
logging!(trace, Type::Core, "Initied core");
#[cfg(target_os = "macos")]
logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await);
Ok(())
}
pub async fn set_running_mode(&self, mode: RunningMode) {
let mut guard = self.running.lock().await;
*guard = mode;
}
pub async fn get_running_mode(&self) -> RunningMode {
let guard = self.running.lock().await;
(*guard).clone()
}
/// 启动核心
pub async fn start_core(&self) -> Result<()> {
if service::is_service_available().await.is_ok() {
if service::check_service_needs_reinstall().await {
service::reinstall_service().await?;
}
logging!(info, Type::Core, true, "服务可用,使用服务模式启动");
self.start_core_by_service().await?;
} else {
// 服务不可用,检查用户偏好
let service_state = service::ServiceState::get();
if service_state.prefer_sidecar {
logging!(
info,
Type::Core,
true,
"服务不可用根据用户偏好使用Sidecar模式"
);
self.start_core_by_sidecar().await?;
} else {
logging!(info, Type::Core, true, "服务不可用使用Sidecar模式");
self.start_core_by_sidecar().await?;
}
}
Ok(())
}
/// 停止核心运行
pub async fn stop_core(&self) -> Result<()> {
match self.get_running_mode().await {
RunningMode::Service => self.stop_core_by_service().await,
RunningMode::Sidecar => self.stop_core_by_sidecar().await,
RunningMode::NotRunning => Ok(()),
}
}
/// 重启内核
pub async fn restart_core(&self) -> Result<()> {
self.stop_core().await?;
self.start_core().await?;
Ok(())
}
/// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<(), String> {
if clash_core.is_none() {
let error_message = "Clash core should not be Null";
logging!(error, Type::Core, true, "{}", error_message);
return Err(error_message.to_string());
}
let core: &str = &clash_core.clone().unwrap();
if !CLASH_CORES.contains(&core) {
let error_message = format!("Clash core invalid name: {}", core);
logging!(error, Type::Core, true, "{}", error_message);
return Err(error_message);
}
Config::verge().draft().clash_core = clash_core.clone();
Config::verge().apply();
logging_error!(Type::Core, true, Config::verge().latest().save_file());
let run_path = Config::generate_file(ConfigType::Run).map_err(|e| {
let msg = e.to_string();
logging_error!(Type::Core, true, "{}", msg);
msg
})?;
self.put_configs_force(run_path).await?;
Ok(())
}
}

View File

@ -1,13 +1,24 @@
use crate::log_err;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::sync::Arc;
use std::{sync::Arc, time::Duration};
use tauri::{AppHandle, Emitter, Manager, WebviewWindow};
use crate::{logging, logging_error, utils::logging::Type};
/// 存储启动期间的错误消息
#[derive(Debug, Clone)]
struct ErrorMessage {
status: String,
message: String,
}
#[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>>,
}
impl Handle {
@ -17,6 +28,8 @@ impl Handle {
HANDLE.get_or_init(|| Handle {
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)),
})
}
@ -40,26 +53,115 @@ impl Handle {
pub fn refresh_clash() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-clash-config", "yes"));
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-clash-config", "yes")
);
}
}
pub fn refresh_verge() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-verge-config", "yes"));
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-verge-config", "yes")
);
}
}
#[allow(unused)]
pub fn refresh_profiles() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-profiles-config", "yes"));
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-profiles-config", "yes")
);
}
}
/// 通知前端显示消息,如果在启动过程中,则将消息存入启动错误队列
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
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 let Some(window) = handle.get_window() {
logging_error!(
Type::Frontend,
true,
window.emit("verge://notice-message", (status_str, 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()
);
// 等待2秒以确保前端已完全加载延迟发送错误通知
if let Some(window) = self.get_window() {
let window_clone = window.clone();
let errors_clone = errors.clone();
tauri::async_runtime::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await;
for error in errors_clone {
let _ =
window_clone.emit("verge://notice-message", (error.status, error.message));
// 每条消息之间间隔500ms避免消息堆积
tokio::time::sleep(Duration::from_millis(500)).await;
}
});
}
}

View File

@ -1,13 +1,16 @@
use crate::core::handle;
use crate::{config::Config, feat, log_err};
use crate::utils::resolve;
use crate::{
config::Config,
core::handle,
feat, logging, logging_error,
module::lightweight::entry_lightweight_mode,
utils::{logging::Type, 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::{async_runtime, Manager};
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
use tauri::async_runtime;
pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
@ -26,19 +29,27 @@ impl Hotkey {
let verge = Config::verge();
let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true);
println!("Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
logging!(
debug,
Type::Hotkey,
true,
"Initializing hotkeys with enable: {}",
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() {
println!("Found {} hotkeys to register", hotkeys.len());
log::info!(target: "app", "Found {} hotkeys to register", hotkeys.len());
logging!(
debug,
Type::Hotkey,
true,
"Has {} hotkeys need to register",
hotkeys.len()
);
for hotkey in hotkeys.iter() {
let mut iter = hotkey.split(',');
@ -47,28 +58,52 @@ impl Hotkey {
match (key, func) {
(Some(key), Some(func)) => {
println!("Registering hotkey: {} -> {}", key, func);
log::info!(target: "app", "Registering hotkey: {} -> {}", key, func);
logging!(
debug,
Type::Hotkey,
true,
"Registering hotkey: {} -> {}",
key,
func
);
if let Err(e) = self.register(key, func) {
println!("Failed to register hotkey {} -> {}: {:?}", key, func, e);
log::error!(target: "app", "Failed to register hotkey {} -> {}: {:?}", key, func, e);
logging!(
error,
Type::Hotkey,
true,
"Failed to register hotkey {} -> {}: {:?}",
key,
func,
e
);
} else {
println!("Successfully registered hotkey {} -> {}", key, func);
log::info!(target: "app", "Successfully registered hotkey {} -> {}", key, func);
logging!(
debug,
Type::Hotkey,
true,
"Successfully registered hotkey {} -> {}",
key,
func
);
}
}
_ => {
let key = key.unwrap_or("None");
let func = func.unwrap_or("None");
println!("Invalid hotkey configuration: `{key}`:`{func}`");
log::error!(target: "app", "Invalid hotkey configuration: `{key}`:`{func}`");
logging!(
error,
Type::Hotkey,
true,
"Invalid hotkey configuration: `{}`:`{}`",
key,
func
);
}
}
}
self.current.lock().clone_from(hotkeys);
} else {
println!("No hotkeys configured");
log::info!(target: "app", "No hotkeys configured");
logging!(debug, Type::Hotkey, true, "No hotkeys configured");
}
Ok(())
@ -85,39 +120,60 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
println!("Attempting to register hotkey: {} for function: {}", hotkey, func);
log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func);
logging!(
debug,
Type::Hotkey,
true,
"Attempting to register hotkey: {} for function: {}",
hotkey,
func
);
if manager.is_registered(hotkey) {
println!("Hotkey {} was already registered, unregistering first", hotkey);
log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey);
logging!(
debug,
Type::Hotkey,
true,
"Hotkey {} was already registered, unregistering first",
hotkey
);
manager.unregister(hotkey)?;
}
let f = match func.trim() {
"open_or_close_dashboard" => {
println!("Registering open_or_close_dashboard function");
log::info!(target: "app", "Registering open_or_close_dashboard function");
logging!(
debug,
Type::Hotkey,
true,
"Registering open_or_close_dashboard function"
);
|| {
println!("=== Hotkey Dashboard Window Operation Start ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation Start ===");
logging!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation Start ==="
);
// 使用 spawn_blocking 来确保在正确的线程上执行
async_runtime::spawn_blocking(|| {
println!("Toggle dashboard window visibility");
log::info!(target: "app", "Toggle dashboard window visibility");
logging!(
debug,
Type::Hotkey,
true,
"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");
logging!(info, Type::Window, true, "Window is visible, hiding it");
let _ = window.hide();
} else {
// 如果窗口不可见,则显示它
println!("Window is hidden, showing it");
log::info!(target: "app", "Window is hidden, showing it");
logging!(info, Type::Window, true, "Window is hidden, showing it");
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
@ -126,26 +182,36 @@ impl Hotkey {
}
} 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();
logging!(
info,
Type::Window,
true,
"Window does not exist, creating a new one"
);
resolve::create_window(true);
}
});
println!("=== Hotkey Dashboard Window Operation End ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ===");
logging!(
debug,
Type::Hotkey,
true,
"=== 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(Some(0)),
#[cfg(target_os = "macos")]
"hide" => || feat::hide(),
_ => {
println!("Invalid function: {}", func);
log::error!(target: "app", "Invalid function: {}", func);
logging!(error, Type::Hotkey, true, "Invalid function: {}", func);
bail!("invalid function \"{func}\"");
}
};
@ -154,28 +220,26 @@ impl Hotkey {
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey, event| {
if event.state == ShortcutState::Pressed {
println!("Hotkey pressed: {:?}", hotkey);
log::info!(target: "app", "Hotkey pressed: {:?}", hotkey);
logging!(debug, Type::Hotkey, true, "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) {
println!("Executing quit function");
log::info!(target: "app", "Executing quit function");
logging!(debug, Type::Hotkey, true, "Executing quit function");
f();
}
}
} else {
// 直接执行函数,不做任何状态检查
println!("Executing function directly");
log::info!(target: "app", "Executing function directly");
logging!(debug, Type::Hotkey, true, "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);
// 获取全局热键状态
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
// 在轻量模式下或配置了全局热键时,始终执行热键功能
if is_lite_mode || is_enable_global_hotkey {
if is_enable_global_hotkey {
f();
} else if let Some(window) = app_handle.get_webview_window("main") {
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
@ -190,8 +254,14 @@ impl Hotkey {
}
});
println!("Successfully registered hotkey {} for {}", hotkey, func);
log::info!(target: "app", "Successfully registered hotkey {} for {}", hotkey, func);
logging!(
debug,
Type::Hotkey,
true,
"Successfully registered hotkey {} for {}",
hotkey,
func
);
Ok(())
}
@ -199,7 +269,7 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
manager.unregister(hotkey)?;
log::debug!(target: "app", "unregister hotkey {hotkey}");
logging!(debug, Type::Hotkey, true, "Unregister hotkey {}", hotkey);
Ok(())
}
@ -215,7 +285,7 @@ impl Hotkey {
});
add.iter().for_each(|(key, func)| {
log_err!(self.register(key, func));
logging_error!(Type::Hotkey, true, self.register(key, func));
});
*current = new_hotkeys;
@ -272,7 +342,13 @@ 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() {
log::error!(target:"app", "Error unregistering all hotkeys: {:?}", e);
logging!(
error,
Type::Hotkey,
true,
"Error unregistering all hotkeys: {:?}",
e
);
}
}
}

View File

@ -1,5 +1,4 @@
pub mod backup;
pub mod clash_api;
#[allow(clippy::module_inception)]
mod core;
pub mod handle;

View File

@ -1,16 +1,95 @@
use crate::config::Config;
use crate::utils::dirs;
use crate::{
config::Config,
logging,
utils::{dirs, logging::Type},
};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::{env::current_exe, process::Command as StdCommand};
use std::{
collections::HashMap,
env::current_exe,
path::PathBuf,
process::Command as StdCommand,
time::{SystemTime, UNIX_EPOCH},
};
use tokio::time::Duration;
// Windows only
const SERVICE_URL: &str = "http://127.0.0.1:33211";
const REQUIRED_SERVICE_VERSION: &str = "1.0.1"; // 定义所需的服务版本号
const REQUIRED_SERVICE_VERSION: &str = "1.0.5"; // 定义所需的服务版本号
// 限制重装时间和次数的常量
const REINSTALL_COOLDOWN_SECS: u64 = 300; // 5分钟冷却期
const MAX_REINSTALLS_PER_DAY: u32 = 3; // 每24小时最多重装3次
const ONE_DAY_SECS: u64 = 86400; // 24小时的秒数
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct ServiceState {
pub last_install_time: u64, // 上次安装时间戳 (Unix 时间戳,秒)
pub install_count: u32, // 24小时内安装次数
pub last_check_time: u64, // 上次检查时间
pub last_error: Option<String>, // 上次错误信息
pub prefer_sidecar: bool, // 用户是否偏好sidecar模式如拒绝安装服务或安装失败
}
impl ServiceState {
// 获取当前的服务状态
pub fn get() -> Self {
if let Some(state) = Config::verge().latest().service_state.clone() {
return state;
}
Self::default()
}
// 保存服务状态
pub fn save(&self) -> Result<()> {
let config = Config::verge();
let mut latest = config.latest().clone();
latest.service_state = Some(self.clone());
*config.draft() = latest;
config.apply();
Config::verge().latest().save_file()
}
// 更新安装信息
pub fn record_install(&mut self) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
// 检查是否需要重置计数器24小时已过
if now - self.last_install_time > ONE_DAY_SECS {
self.install_count = 0;
}
self.last_install_time = now;
self.install_count += 1;
}
// 检查是否可以重新安装
pub fn can_reinstall(&self) -> bool {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
// 如果在冷却期内,不允许重装
if now - self.last_install_time < REINSTALL_COOLDOWN_SECS {
return false;
}
// 如果24小时内安装次数过多也不允许
if now - self.last_install_time < ONE_DAY_SECS
&& self.install_count >= MAX_REINSTALLS_PER_DAY
{
return false;
}
true
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ResponseBody {
@ -41,34 +120,56 @@ pub struct VersionJsonResponse {
}
#[cfg(target_os = "windows")]
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
pub async fn uninstall_service() -> Result<()> {
logging!(info, Type::Service, true, "uninstall service");
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::os::windows::process::CommandExt;
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service.exe");
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let _ = match level {
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?,
_ => StdCommand::new(uninstall_path)
.creation_flags(0x08000000)
.status()?,
};
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "windows")]
pub async fn install_service() -> Result<()> {
logging!(info, Type::Service, true, "install service");
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::os::windows::process::CommandExt;
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service.exe");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?,
_ => StdCommand::new(install_path)
@ -86,24 +187,64 @@ pub async fn reinstall_service() -> Result<()> {
Ok(())
}
#[cfg(target_os = "linux")]
#[cfg(target_os = "windows")]
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
logging!(info, Type::Service, true, "reinstall service");
// 获取当前服务状态
let mut service_state = ServiceState::get();
// 检查是否允许重装
if !service_state.can_reinstall() {
logging!(
warn,
Type::Service,
true,
"service reinstall rejected: cooldown period or max attempts reached"
);
bail!("Service reinstallation is rate limited. Please try again later.");
}
// 先卸载服务
if let Err(err) = uninstall_service().await {
logging!(
warn,
Type::Service,
true,
"failed to uninstall service: {}",
err
);
}
// 再安装服务
match install_service().await {
Ok(_) => {
// 记录安装信息并保存
service_state.record_install();
service_state.last_error = None;
service_state.save()?;
Ok(())
}
Err(err) => {
let error = format!("failed to install service: {}", err);
service_state.last_error = Some(error.clone());
service_state.save()?;
bail!(error)
}
}
}
#[cfg(target_os = "linux")]
pub async fn uninstall_service() -> Result<()> {
logging!(info, Type::Service, true, "uninstall service");
use users::get_effective_uid;
let install_path = tauri::utils::platform::current_exe()?.with_file_name("install-service");
let uninstall_path = tauri::utils::platform::current_exe()?.with_file_name("uninstall-service");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().replace(" ", "\\ ");
let uninstall_shell: String = uninstall_path.to_string_lossy().replace(" ", "\\ ");
let elevator = crate::utils::help::linux_elevator();
@ -115,8 +256,38 @@ pub async fn reinstall_service() -> Result<()> {
.arg(uninstall_shell)
.status()?,
};
log::info!(target:"app", "status code:{}", status.code().unwrap());
logging!(
info,
Type::Service,
true,
"uninstall status code:{}",
status.code().unwrap()
);
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn install_service() -> Result<()> {
logging!(info, Type::Service, true, "install service");
use users::get_effective_uid;
let install_path = tauri::utils::platform::current_exe()?.with_file_name("install-service");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().replace(" ", "\\ ");
let elevator = crate::utils::help::linux_elevator();
let status = match get_effective_uid() {
0 => StdCommand::new(install_shell).status()?,
_ => StdCommand::new(elevator.clone())
@ -125,6 +296,132 @@ pub async fn reinstall_service() -> Result<()> {
.arg(install_shell)
.status()?,
};
logging!(
info,
Type::Service,
true,
"install status code:{}",
status.code().unwrap()
);
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn reinstall_service() -> Result<()> {
logging!(info, Type::Service, true, "reinstall service");
// 获取当前服务状态
let mut service_state = ServiceState::get();
// 检查是否允许重装
if !service_state.can_reinstall() {
logging!(
warn,
Type::Service,
true,
"service reinstall rejected: cooldown period or max attempts reached"
);
bail!("Service reinstallation is rate limited. Please try again later.");
}
// 先卸载服务
if let Err(err) = uninstall_service().await {
logging!(
warn,
Type::Service,
true,
"failed to uninstall service: {}",
err
);
}
// 再安装服务
match install_service().await {
Ok(_) => {
// 记录安装信息并保存
service_state.record_install();
service_state.last_error = None;
service_state.save()?;
Ok(())
}
Err(err) => {
let error = format!("failed to install service: {}", err);
service_state.last_error = Some(error.clone());
service_state.save()?;
bail!(error)
}
}
}
#[cfg(target_os = "macos")]
pub async fn uninstall_service() -> Result<()> {
use crate::utils::i18n::t;
logging!(info, Type::Service, true, "uninstall service");
let binary_path = dirs::service_path()?;
let uninstall_path = binary_path.with_file_name("uninstall-service");
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned();
let prompt = t("Service Administrator Prompt");
let command = format!(
r#"do shell script "sudo '{uninstall_shell}'" with administrator privileges with prompt "{prompt}""#
);
logging!(debug, Type::Service, true, "uninstall command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "macos")]
pub async fn install_service() -> Result<()> {
use crate::utils::i18n::t;
logging!(info, Type::Service, true, "install service");
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().into_owned();
let prompt = t("Service Administrator Prompt");
let command = format!(
r#"do shell script "sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""#
);
logging!(debug, Type::Service, true, "install command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
@ -138,39 +435,49 @@ pub async fn reinstall_service() -> Result<()> {
#[cfg(target_os = "macos")]
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
logging!(info, Type::Service, true, "reinstall service");
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service");
let uninstall_path = binary_path.with_file_name("uninstall-service");
// 获取当前服务状态
let mut service_state = ServiceState::get();
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().into_owned();
let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned();
let command = format!(
r#"do shell script "sudo '{uninstall_shell}' && sudo '{install_shell}'" with administrator privileges"#
// 检查是否允许重装
if !service_state.can_reinstall() {
logging!(
warn,
Type::Service,
true,
"service reinstall rejected: cooldown period or max attempts reached"
);
bail!("Service reinstallation is rate limited. Please try again later.");
}
log::debug!(target: "app", "command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
// 先卸载服务
if let Err(err) = uninstall_service().await {
logging!(
warn,
Type::Service,
true,
"failed to uninstall service: {}",
err
);
}
// 再安装服务
match install_service().await {
Ok(_) => {
// 记录安装信息并保存
service_state.record_install();
service_state.last_error = None;
service_state.save()?;
Ok(())
}
Err(err) => {
let error = format!("failed to install service: {}", err);
service_state.last_error = Some(error.clone());
service_state.save()?;
bail!(error)
}
}
}
/// check the windows service status
@ -214,19 +521,54 @@ pub async fn check_service_version() -> Result<String> {
/// check if service needs to be reinstalled
pub async fn check_service_needs_reinstall() -> bool {
// 获取当前服务状态
let service_state = ServiceState::get();
// 首先检查是否在冷却期或超过重装次数限制
if !service_state.can_reinstall() {
log::info!(target: "app", "service reinstall check: in cooldown period or max attempts reached");
return false;
}
// 然后才检查版本和可用性
match check_service_version().await {
Ok(version) => version != REQUIRED_SERVICE_VERSION,
Err(_) => true, // 如果无法获取版本或服务未运行,也需要重新安装
Ok(version) => {
// 打印更详细的日志,方便排查问题
log::info!(target: "app", "服务版本检测:当前={}, 要求={}", version, REQUIRED_SERVICE_VERSION);
let needs_reinstall = version != REQUIRED_SERVICE_VERSION;
if needs_reinstall {
log::warn!(target: "app", "发现服务版本不匹配,需要重装! 当前={}, 要求={}",
version, REQUIRED_SERVICE_VERSION);
// 打印版本字符串的原始字节,确认没有隐藏字符
log::debug!(target: "app", "当前版本字节: {:?}", version.as_bytes());
log::debug!(target: "app", "要求版本字节: {:?}", REQUIRED_SERVICE_VERSION.as_bytes());
} else {
log::info!(target: "app", "服务版本匹配,无需重装");
}
needs_reinstall
}
Err(err) => {
// 检查服务是否可用如果可用但版本检查失败可能只是版本API有问题
match is_service_running().await {
Ok(true) => {
log::info!(target: "app", "service is running but version check failed: {}", err);
false // 服务在运行,不需要重装
}
_ => {
log::info!(target: "app", "service is not running or unavailable");
true // 服务不可用,需要重装
}
}
}
}
}
/// start the clash by service
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
// 检查服务版本,如果不匹配则重新安装
if check_service_needs_reinstall().await {
log::info!(target: "app", "service version mismatch, reinstalling");
reinstall_service().await?;
}
/// 尝试使用现有服务启动核心,不进行重装
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
log::info!(target:"app", "attempting to start core with existing service");
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
@ -266,6 +608,106 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
Ok(())
}
/// start the clash by service
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
log::info!(target: "app", "正在尝试通过服务启动核心");
// 先检查服务版本,不受冷却期限制
let version_check = match check_service_version().await {
Ok(version) => {
log::info!(target: "app", "检测到服务版本: {}, 要求版本: {}",
version, REQUIRED_SERVICE_VERSION);
// 通过字节比较确保完全匹配
if version.as_bytes() != REQUIRED_SERVICE_VERSION.as_bytes() {
log::warn!(target: "app", "服务版本不匹配,需要重装");
false // 版本不匹配
} else {
log::info!(target: "app", "服务版本匹配");
true // 版本匹配
}
}
Err(err) => {
log::warn!(target: "app", "无法获取服务版本: {}", err);
false // 无法获取版本
}
};
// 先尝试直接启动服务,如果服务可用且版本匹配
if version_check {
if let Ok(true) = is_service_running().await {
// 服务正在运行且版本匹配,直接使用
log::info!(target: "app", "服务已在运行且版本匹配,尝试使用");
return start_with_existing_service(config_file).await;
}
}
// 强制执行版本检查,如果版本不匹配则重装
if !version_check {
log::info!(target: "app", "服务版本不匹配,尝试重装");
// 获取服务状态,检查是否可以重装
let service_state = ServiceState::get();
if !service_state.can_reinstall() {
log::warn!(target: "app", "由于限制无法重装服务");
// 尝试直接启动,即使版本不匹配
if let Ok(()) = start_with_existing_service(config_file).await {
log::info!(target: "app", "尽管版本不匹配,但成功启动了服务");
return Ok(());
} else {
bail!("服务版本不匹配且无法重装,启动失败");
}
}
// 尝试重装
log::info!(target: "app", "开始重装服务");
if let Err(err) = reinstall_service().await {
log::warn!(target: "app", "服务重装失败: {}", err);
// 尝试使用现有服务
log::info!(target: "app", "尝试使用现有服务");
return start_with_existing_service(config_file).await;
}
// 重装成功,尝试启动
log::info!(target: "app", "服务重装成功,尝试启动");
return start_with_existing_service(config_file).await;
}
// 检查服务状态
match check_service().await {
Ok(_) => {
// 服务可访问但可能没有运行核心,尝试直接启动
log::info!(target: "app", "服务可用但未运行核心,尝试启动");
if let Ok(()) = start_with_existing_service(config_file).await {
return Ok(());
}
}
Err(err) => {
log::warn!(target: "app", "服务检查失败: {}", err);
}
}
// 服务不可用或启动失败,检查是否需要重装
if check_service_needs_reinstall().await {
log::info!(target: "app", "服务需要重装");
// 尝试重装
if let Err(err) = reinstall_service().await {
log::warn!(target: "app", "服务重装失败: {}", err);
bail!("Failed to reinstall service: {}", err);
}
// 重装后再次尝试启动
log::info!(target: "app", "服务重装完成,尝试启动核心");
start_with_existing_service(config_file).await
} else {
// 不需要或不能重装,返回错误
log::warn!(target: "app", "服务不可用且无法重装");
bail!("Service is not available and cannot be reinstalled at this time")
}
}
/// stop the clash by service
pub(super) async fn stop_core_by_service() -> Result<()> {
let url = format!("{SERVICE_URL}/stop_clash");
@ -279,3 +721,48 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
Ok(())
}
/// 检查服务是否正在运行
pub async fn is_service_running() -> Result<bool> {
let resp = check_service().await?;
// 检查服务状态码和消息
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(debug, Type::Service, "Service is running");
Ok(true)
} else {
logging!(debug, Type::Service, "Service is not running");
Ok(false)
}
}
pub async fn is_service_available() -> Result<()> {
let resp = check_service().await?;
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(debug, Type::Service, "Service is available");
}
Ok(())
}
/// 强制重装服务用于UI中的修复服务按钮
pub async fn force_reinstall_service() -> Result<()> {
log::info!(target: "app", "用户请求强制重装服务");
// 创建默认服务状态(重置所有限制)
let service_state = ServiceState::default();
service_state.save()?;
log::info!(target: "app", "已重置服务状态,开始执行重装");
// 执行重装
match reinstall_service().await {
Ok(()) => {
log::info!(target: "app", "服务重装成功");
Ok(())
}
Err(err) => {
log::error!(target: "app", "强制重装服务失败: {}", err);
bail!("强制重装服务失败: {}", err)
}
}
}

View File

@ -1,7 +1,8 @@
use crate::core::handle::Handle;
use crate::{
config::{Config, IVerge},
log_err,
core::handle::Handle,
logging_error,
utils::logging::Type,
};
use anyhow::Result;
use once_cell::sync::OnceCell;
@ -126,8 +127,7 @@ impl Sysopt {
if !sys_enable {
return self.reset_sysproxy().await;
}
use crate::core::handle::Handle;
use crate::utils::dirs;
use crate::{core::handle::Handle, utils::dirs};
use anyhow::bail;
use tauri_plugin_shell::ShellExt;
@ -185,8 +185,7 @@ impl Sysopt {
#[cfg(target_os = "windows")]
{
use crate::core::handle::Handle;
use crate::utils::dirs;
use crate::{core::handle::Handle, utils::dirs};
use anyhow::bail;
use tauri_plugin_shell::ShellExt;
@ -222,15 +221,50 @@ impl Sysopt {
let enable = enable.unwrap_or(false);
let app_handle = Handle::global().app_handle().unwrap();
let autostart_manager = app_handle.autolaunch();
println!("enable: {}", enable);
log::info!(target: "app", "Setting auto launch to: {}", enable);
match enable {
true => log_err!(autostart_manager.enable()),
false => log_err!(autostart_manager.disable()),
true => {
let result = autostart_manager.enable();
if let Err(ref e) = result {
log::error!(target: "app", "Failed to enable auto launch: {}", e);
} else {
log::info!(target: "app", "Auto launch enabled successfully");
}
logging_error!(Type::System, true, result);
}
false => {
let result = autostart_manager.disable();
if let Err(ref e) = result {
log::error!(target: "app", "Failed to disable auto launch: {}", e);
} else {
log::info!(target: "app", "Auto launch disabled successfully");
}
logging_error!(Type::System, true, result);
}
};
Ok(())
}
/// 获取当前自启动的实际状态
pub fn get_launch_status(&self) -> Result<bool> {
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();
@ -290,7 +324,7 @@ impl Sysopt {
enable: true,
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
log_err!(autoproxy.set_auto_proxy());
logging_error!(Type::System, true, autoproxy.set_auto_proxy());
} else {
let sysproxy = Sysproxy {
enable: true,
@ -299,14 +333,13 @@ impl Sysopt {
bypass: get_bypass(),
};
log_err!(sysproxy.set_system_proxy());
logging_error!(Type::System, true, sysproxy.set_system_proxy());
}
}
#[cfg(target_os = "windows")]
{
use crate::core::handle::Handle;
use crate::utils::dirs;
use crate::{core::handle::Handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap();

View File

@ -1,24 +1,34 @@
use crate::config::Config;
use crate::feat;
use crate::core::CoreManager;
use crate::{
config::Config, core::CoreManager, feat, logging, logging_error, utils::logging::Type,
};
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, 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
delay_timer: Arc<Mutex<DelayTimer>>,
pub delay_timer: Arc<RwLock<DelayTimer>>,
/// save the current state
timer_map: Arc<Mutex<HashMap<String, (TaskID, u64)>>>,
/// save the current state - using RwLock for better read concurrency
pub timer_map: Arc<RwLock<HashMap<String, TimerTask>>>,
/// increment id
timer_count: Arc<Mutex<TaskID>>,
/// 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>,
}
impl Timer {
@ -26,68 +36,164 @@ impl Timer {
static TIMER: OnceCell<Timer> = OnceCell::new();
TIMER.get_or_init(|| Timer {
delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(Mutex::new(HashMap::new())),
delay_timer: Arc::new(RwLock::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(RwLock::new(HashMap::new())),
timer_count: Arc::new(Mutex::new(1)),
initialized: Arc::new(std::sync::atomic::AtomicBool::new(false)),
})
}
/// restore timer
/// Initialize timer with better error handling and atomic operations
pub fn init(&self) -> Result<()> {
self.refresh()?;
// 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 cur_timestamp = chrono::Local::now().timestamp();
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
if let Some(items) = Config::profiles().latest().get_items() {
// Collect profiles that need immediate update
let profiles_to_update = if let Some(items) = Config::profiles().latest().get_items() {
items
.iter()
.filter_map(|item| {
// mins to seconds
let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60;
let interval = item.option.as_ref()?.update_interval? as i64;
let updated = item.updated? as i64;
let uid = item.uid.as_ref()?;
if interval > 0 && cur_timestamp - updated >= interval {
Some(item)
if interval > 0 && cur_timestamp - updated >= interval * 60 {
Some(uid.clone())
} else {
None
}
})
.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));
.collect::<Vec<String>>()
} else {
Vec::new()
};
// Advance tasks outside of locks to minimize lock contention
if !profiles_to_update.is_empty() {
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, "Advancing task for uid: {}", uid);
if let Err(e) = delay_timer.advance_task(task.task_id) {
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
}
}
}
})
}
logging!(info, Type::Timer, "Timer initialization completed");
Ok(())
}
/// Correctly update all cron tasks
/// Refresh timer tasks with better error handling
pub fn refresh(&self) -> Result<()> {
// Generate diff outside of lock to minimize lock contention
let diff_map = self.gen_diff();
let mut timer_map = self.timer_map.lock();
let mut delay_timer = self.delay_timer.lock();
if diff_map.is_empty() {
logging!(debug, Type::Timer, "No timer changes needed");
return Ok(());
}
for (uid, diff) in diff_map.into_iter() {
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 {
match diff {
DiffFlag::Del(tid) => {
let _ = timer_map.remove(&uid);
crate::log_err!(delay_timer.remove_task(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);
}
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, 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));
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::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);
}
}
}
}
@ -95,18 +201,17 @@ impl Timer {
Ok(())
}
/// generate a uid -> update_interval map
/// Generate map of profile UIDs to update intervals
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 item.option.is_some() {
let option = item.option.as_ref().unwrap();
let interval = option.update_interval.unwrap_or(0);
if let Some(option) = item.option.as_ref() {
if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) {
if interval > 0 {
new_map.insert(item.uid.clone().unwrap(), interval);
new_map.insert(uid.clone(), interval);
}
}
}
}
@ -115,39 +220,50 @@ impl Timer {
new_map
}
/// generate the diff map for refresh
/// Generate differences between current and new timer configuration
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;
cur_map.iter().for_each(|(uid, (tid, val))| {
let new_val = new_map.get(uid).unwrap_or(&0);
// Read lock for comparing current state
let timer_map = self.timer_map.read();
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 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
diff_map.insert(uid.clone(), DiffFlag::Mod(task.task_id, interval));
}
});
let mut count = self.timer_count.lock();
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;
None => {
// Task no longer needed
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
}
_ => {
// Task exists with same interval, no change needed
}
}
}
// Find new tasks to add
let mut next_id = *self.timer_count.lock();
for (uid, &interval) in new_map.iter() {
if !timer_map.contains_key(uid) {
diff_map.insert(uid.clone(), DiffFlag::Add(next_id, interval));
next_id += 1;
}
}
// Update counter only if we added new tasks
if next_id > *self.timer_count.lock() {
*self.timer_count.lock() = next_id;
}
});
diff_map
}
/// add a cron task
/// Add a timer task with better error handling
fn add_task(
&self,
delay_timer: &mut DelayTimer,
@ -155,12 +271,26 @@ 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)
// .set_frequency_repeated_by_seconds(minutes) // for test
.spawn_async_routine(move || Self::async_task(uid.to_owned()))
.spawn_async_routine(move || {
let uid = uid.clone();
async move {
Self::async_task(uid).await;
}
})
.context("failed to create timer task")?;
delay_timer
@ -170,19 +300,41 @@ impl Timer {
Ok(())
}
/// the task runner
/// Async task with better error handling and logging
async fn async_task(uid: String) {
log::info!(target: "app", "running timer task `{uid}`");
let task_start = std::time::Instant::now();
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
// 使用更轻量级的更新方式
if let Err(e) = feat::update_profile(uid.clone(), None).await {
log::error!(target: "app", "timer task update error: {}", e);
return;
// Update profile
let profile_result = feat::update_profile(uid.clone(), None).await;
match profile_result {
Ok(_) => {
// Update configuration
match CoreManager::global().update_config().await {
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 refresh config after profile update for uid {}: {}",
uid,
e
);
}
}
}
Err(e) => {
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
}
// 只有更新成功后才刷新配置
if let Err(e) = CoreManager::global().update_config().await {
log::error!(target: "app", "timer task refresh error: {}", e);
}
}
}

View File

@ -1,13 +1,14 @@
use once_cell::sync::OnceCell;
use tauri::tray::TrayIconBuilder;
#[cfg(target_os = "macos")]
pub mod speed_rate;
use crate::core::clash_api::Rate;
use crate::{
cmds,
cmd,
config::Config,
feat, resolve,
utils::resolve::VERSION,
utils::{dirs, i18n::t},
feat,
module::{lightweight::entry_lightweight_mode, mihomo::Rate},
resolve,
utils::{dirs::find_target_icons, i18n::t, logging::Type, resolve::VERSION},
};
use anyhow::Result;
@ -19,29 +20,136 @@ use parking_lot::Mutex;
use parking_lot::RwLock;
#[cfg(target_os = "macos")]
pub use speed_rate::{SpeedRate, Traffic};
use std::fs;
#[cfg(target_os = "macos")]
use std::sync::Arc;
use tauri::menu::{CheckMenuItem, IsMenuItem};
use tauri::AppHandle;
use tauri::{
menu::{MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIconEvent, TrayIconId},
Wry,
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIconEvent},
App, AppHandle, Wry,
};
#[cfg(target_os = "macos")]
use tokio::sync::broadcast;
use super::handle;
#[derive(Clone)]
struct TrayState {}
#[cfg(target_os = "macos")]
pub struct Tray {
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
is_subscribed: Arc<RwLock<bool>>,
pub rate_cache: Arc<Mutex<Option<Rate>>>,
}
#[cfg(not(target_os = "macos"))]
pub struct Tray {}
impl TrayState {
pub fn get_common_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().latest().clone();
let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false);
if is_common_tray_icon {
if let Some(common_icon_path) = find_target_icons("common").unwrap() {
let icon_data = fs::read(common_icon_path).unwrap();
return (true, icon_data);
}
}
#[cfg(target_os = "macos")]
{
let tray_icon_colorful = verge.tray_icon.unwrap_or("monochrome".to_string());
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
)
} else {
(
false,
include_bytes!("../../../icons/tray-icon.ico").to_vec(),
)
}
}
#[cfg(not(target_os = "macos"))]
{
(
false,
include_bytes!("../../../icons/tray-icon.ico").to_vec(),
)
}
}
pub fn get_sysproxy_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().latest().clone();
let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false);
if is_sysproxy_tray_icon {
if let Some(sysproxy_icon_path) = find_target_icons("sysproxy").unwrap() {
let icon_data = fs::read(sysproxy_icon_path).unwrap();
return (true, icon_data);
}
}
#[cfg(target_os = "macos")]
{
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
)
} else {
(
false,
include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
)
}
}
#[cfg(not(target_os = "macos"))]
{
(
false,
include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
)
}
}
pub fn get_tun_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().latest().clone();
let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false);
if is_tun_tray_icon {
if let Some(tun_icon_path) = find_target_icons("tun").unwrap() {
let icon_data = fs::read(tun_icon_path).unwrap();
return (true, icon_data);
}
}
#[cfg(target_os = "macos")]
{
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
)
} else {
(
false,
include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
)
}
}
#[cfg(not(target_os = "macos"))]
{
(
false,
include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
)
}
}
}
impl Tray {
pub fn global() -> &'static Tray {
static TRAY: OnceCell<Tray> = OnceCell::new();
@ -51,6 +159,7 @@ impl Tray {
speed_rate: Arc::new(Mutex::new(None)),
shutdown_tx: Arc::new(RwLock::new(None)),
is_subscribed: Arc::new(RwLock::new(false)),
rate_cache: Arc::new(Mutex::new(None)),
});
#[cfg(not(target_os = "macos"))]
@ -66,19 +175,27 @@ impl Tray {
Ok(())
}
pub fn create_systray(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray_incon_id = TrayIconId::new("main");
let tray = app_handle.tray_by_id(&tray_incon_id).unwrap();
pub fn create_systray(&self, app: &App) -> Result<()> {
let mut builder = TrayIconBuilder::with_id("main")
.icon(app.default_window_icon().unwrap().clone())
.icon_as_template(false);
#[cfg(target_os = "macos")]
tray.set_show_menu_on_left_click(false)?;
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
if tray_event.as_str() != "tray_menu" {
builder = builder.show_menu_on_left_click(false);
}
}
let tray = builder.build(app)?;
tray.on_tray_icon_event(|_, event| {
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
log::debug!(target: "app","tray event: {:?}", tray_event);
#[cfg(target_os = "macos")]
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
@ -88,22 +205,7 @@ impl Tray {
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(),
_ => {}
}
}
#[cfg(not(target_os = "macos"))]
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
..
} = event
{
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(),
"main_window" => resolve::create_window(true),
_ => {}
}
}
@ -112,6 +214,19 @@ impl Tray {
Ok(())
}
/// 更新托盘点击行为
pub fn update_click_behavior(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
let tray = app_handle.tray_by_id("main").unwrap();
match tray_event.as_str() {
"tray_menu" => tray.set_show_menu_on_left_click(true)?,
_ => tray.set_show_menu_on_left_click(false)?,
}
Ok(())
}
/// 更新托盘菜单
pub fn update_menu(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
@ -130,7 +245,7 @@ impl Tray {
let profile_uid_and_name = Config::profiles()
.data()
.all_profile_uid_and_name()
.unwrap_or(Vec::new());
.unwrap_or_default();
let tray = app_handle.tray_by_id("main").unwrap();
let _ = tray.set_menu(Some(create_tray_menu(
@ -144,115 +259,68 @@ impl Tray {
}
/// 更新托盘图标
#[allow(unused_variables)]
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let verge = Config::verge().latest().clone();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray = app_handle.tray_by_id("main").unwrap();
#[cfg(target_os = "macos")]
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
let icon_bytes = if *system_proxy && !*tun_mode {
#[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() {
"colorful" => include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
_ => include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
(true, true) => TrayState::get_tun_tray_icon(),
(true, false) => TrayState::get_sysproxy_tray_icon(),
(false, true) => TrayState::get_tun_tray_icon(),
(false, false) => TrayState::get_common_tray_icon(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../../icons/tray-icon-sys.ico").to_vec();
if *sysproxy_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("sysproxy.png");
let ico_path = icon_dir_path.join("sysproxy.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
} else if *tun_mode {
#[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() {
"colorful" => include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
_ => include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../../icons/tray-icon-tun.ico").to_vec();
if *tun_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("tun.png");
let ico_path = icon_dir_path.join("tun.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
} else {
#[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() {
"colorful" => include_bytes!("../../../icons/tray-icon.ico").to_vec(),
_ => include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../../icons/tray-icon.ico").to_vec();
if *common_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("common.png");
let ico_path = icon_dir_path.join("common.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
};
#[cfg(target_os = "macos")]
{
let enable_tray_speed = Config::verge().latest().enable_tray_speed.unwrap_or(true);
let is_colorful = tray_icon == "colorful";
let enable_tray_speed = verge.enable_tray_speed.unwrap_or(true);
let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true);
let colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
let is_colorful = colorful == "colorful";
// 处理图标和速率
let final_icon_bytes = if enable_tray_speed {
let rate = rate.or_else(|| {
self.speed_rate
.lock()
.as_ref()
.and_then(|speed_rate| speed_rate.get_curent_rate())
});
if !enable_tray_speed {
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
let _ = tray.set_icon_as_template(!is_colorful);
return Ok(());
}
// 使用新的方法渲染图标和速率
SpeedRate::add_speed_text(icon_bytes, rate)?
let rate = if let Some(rate) = rate {
Some(rate)
} else {
icon_bytes
let guard = self.speed_rate.lock();
if let Some(rate) = guard.as_ref().unwrap().get_curent_rate() {
Some(rate)
} else {
Some(Rate::default())
}
};
// 设置系统托盘图标
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&final_icon_bytes)?));
// 只对单色图标使用 template 模式
let _ = tray.set_icon_as_template(!is_colorful);
let mut rate_guard = self.rate_cache.lock();
if *rate_guard != rate {
*rate_guard = rate;
let bytes = if enable_tray_icon {
Some(icon_bytes)
} else {
None
};
let rate = rate_guard.as_ref();
let rate_bytes = SpeedRate::add_speed_text(is_custom_icon, bytes, rate).unwrap();
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
{
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
Ok(())
}
}
/// 更新托盘提示
pub fn update_tooltip(&self) -> Result<()> {
@ -399,17 +467,20 @@ fn create_tray_menu(
let profile_menu_items: Vec<CheckMenuItem<Wry>> = profile_uid_and_name
.iter()
.map(|(profile_uid, profile_name)| {
let is_current_profile = Config::profiles().data().is_current_profile_index(profile_uid.to_string());
let is_current_profile = Config::profiles()
.data()
.is_current_profile_index(profile_uid.to_string());
CheckMenuItem::with_id(
app_handle,
&format!("profiles_{}", profile_uid),
t(&profile_name),
format!("profiles_{}", profile_uid),
t(profile_name),
true,
is_current_profile,
None::<&str>,
)
.unwrap()
}).collect();
})
.collect();
let profile_menu_items: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
.iter()
.map(|item| item as &dyn IsMenuItem<Wry>)
@ -460,7 +531,8 @@ fn create_tray_menu(
t("Profiles"),
true,
&profile_menu_items,
).unwrap();
)
.unwrap();
let system_proxy = &CheckMenuItem::with_id(
app_handle,
@ -482,6 +554,15 @@ fn create_tray_menu(
)
.unwrap();
let lighteweight_mode = &MenuItem::with_id(
app_handle,
"entry_lightweight_mode",
t("LightWeight Mode"),
true,
hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()),
)
.unwrap();
let copy_env =
&MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>).unwrap();
@ -574,6 +655,8 @@ fn create_tray_menu(
separator,
system_proxy,
tun_mode,
separator,
lighteweight_mode,
copy_env,
open_dir,
more,
@ -592,19 +675,19 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
println!("change mode to: {}", mode);
feat::change_clash_mode(mode.into());
}
"open_window" => resolve::create_window(),
"open_window" => resolve::create_window(true),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"copy_env" => feat::copy_clash_env(),
"open_app_dir" => crate::log_err!(cmds::open_app_dir()),
"open_core_dir" => crate::log_err!(cmds::open_core_dir()),
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
"open_app_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_app_dir()),
"open_core_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_core_dir()),
"open_logs_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_logs_dir()),
"restart_clash" => feat::restart_clash_core(),
"restart_app" => feat::restart_app(),
"entry_lightweight_mode" => entry_lightweight_mode(),
"quit" => {
println!("quit");
feat::quit(Some(0));
},
}
id if id.starts_with("profiles_") => {
let profile_index = &id["profiles_".len()..];
feat::toggle_proxy_profile(profile_index.into());

View File

@ -1,20 +1,20 @@
use crate::core::clash_api::{get_traffic_ws_url, Rate};
use crate::utils::help::format_bytes_speed;
use crate::{
module::mihomo::{MihomoManager, Rate},
utils::help::format_bytes_speed,
};
use ab_glyph::FontArc;
use anyhow::Result;
use futures::Stream;
use image::{Rgba, GenericImageView, RgbaImage};
use image::{GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut;
use parking_lot::Mutex;
use rusttype::{Font, Scale};
use std::io::Cursor;
use std::sync::Arc;
use tokio_tungstenite::tungstenite::Message;
use std::{io::Cursor, sync::Arc};
use tokio_tungstenite::tungstenite::{http, 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,11 +37,20 @@ impl SpeedRate {
let (current, previous) = &mut *rates;
// 如果速率变化不大小于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
// 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)
};
if !should_update {
@ -67,57 +76,104 @@ impl SpeedRate {
}
// 分离图标加载和速率渲染
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 });
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 });
// 加载原始图标
let icon_image = image::load_from_memory(&icon_bytes)?;
let (icon_width, icon_height) = (icon_image.width(), icon_image.height());
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 is_colorful = !crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
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 text_width = 580; // 文本区域宽度
let total_width = icon_width + text_width;
// println!(
// "icon_height: {}, icon_wight: {}, total_width: {}",
// icon_height, icon_width, total_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);
}
}
}
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([255u8, 255u8, 255u8, 255u8]), Rgba([0u8, 0u8, 0u8, 160u8]))
(
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, 120u8]))
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
)
};
// 减小字体大小以适应文本区域
let font = Font::try_from_bytes(include_bytes!("../../../assets/fonts/SF-Pro.ttf")).unwrap();
let font_data = include_bytes!("../../../assets/fonts/SF-Pro.ttf");
let font = FontArc::try_from_vec(font_data.to_vec()).unwrap();
let font_size = icon_height as f32 * 0.6; // 稍微减小字体
let scale = Scale::uniform(font_size);
let scale = ab_glyph::PxScale::from(font_size);
// 使用更简洁的速率格式
let up_text = format_bytes_speed(rate.up);
let down_text = format_bytes_speed(rate.down);
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_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 = total_width - up_text_width;
// let down_text_x = total_width - down_text_width;
// 计算左对齐的文本位置
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 text_height = font_size as i32;
@ -173,7 +229,6 @@ impl SpeedRate {
combined_image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?;
Ok(bytes)
}
}
#[derive(Debug, Clone)]
@ -191,9 +246,13 @@ impl Traffic {
let stream = Box::pin(
stream::unfold((), |_| async {
loop {
let ws_url = get_traffic_ws_url().unwrap();
let (url, token) = MihomoManager::get_traffic_ws_url();
let mut request = url.into_client_request().unwrap();
request
.headers_mut()
.insert(http::header::AUTHORIZATION, token);
match tokio_tungstenite::connect_async(&ws_url).await {
match tokio_tungstenite::connect_async(request).await {
Ok((ws_stream, _)) => {
log::info!(target: "app", "traffic ws connection established");
return Some((

View File

@ -5,17 +5,10 @@ mod script;
pub mod seq;
mod tun;
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 self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
use crate::{config::Config, utils::tmpl};
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
type ResultLog = Vec<(String, String)>;
@ -25,7 +18,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// config.yaml 的订阅
let clash_config = { Config::clash().latest().0.clone() };
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled) = {
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = {
let verge = Config::verge();
let verge = verge.latest();
(
@ -34,6 +27,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
verge.enable_builtin_enhanced.unwrap_or(true),
verge.verge_socks_enabled.unwrap_or(false),
verge.verge_http_enabled.unwrap_or(false),
verge.enable_dns_settings.unwrap_or(false),
)
};
#[cfg(not(target_os = "windows"))]
@ -262,6 +256,27 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
config = use_tun(config, enable_tun).await;
config = use_sort(config);
// 应用独立的DNS配置如果启用
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) {
// 将DNS配置合并到最终配置中
config.insert("dns".into(), dns_config.into());
log::info!(target: "app", "apply dns_config.yaml");
}
}
}
}
}
let mut exists_set = HashSet::new();
exists_set.extend(exists_keys);
exists_keys = exists_set.into_iter().collect();

View File

@ -85,6 +85,7 @@ pub fn use_seq(seq: SeqMap, mut config: Mapping, field: &str) -> Mapping {
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use serde_yaml::Value;
#[test]
@ -120,16 +121,32 @@ 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,6 +24,9 @@ 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");
let dns_val = config.get(&dns_key);
let mut dns_val = dns_val.map_or(Mapping::new(), |val| {
@ -35,23 +38,22 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
.and_then(|v| v.as_bool())
.unwrap_or(false);
if enable {
// 检查现有的 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");
}
@ -61,42 +63,18 @@ 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 {
revise!(
dns_val,
"enable",
dns_val
.get("enable")
.and_then(|v| v.as_bool())
.unwrap_or(true)
);
revise!(dns_val, "ipv6", ipv6_val);
revise!(
dns_val,
"enhanced-mode",
dns_val
.get("enhanced-mode")
.and_then(|v| v.as_str())
.unwrap_or("redir-host")
);
revise!(
dns_val,
"fake-ip-range",
dns_val
.get("fake-ip-range")
.and_then(|v| v.as_str())
.unwrap_or("198.18.0.1/16")
);
// TUN未启用时仅恢复系统DNS不修改配置文件中的DNS设置
#[cfg(target_os = "macos")]
crate::utils::resolve::restore_public_dns().await;
}
// 更新TUN配置
revise!(tun_val, "enable", enable);
revise!(config, "tun", tun_val);
revise!(config, "dns", dns_val);
config
}

View File

@ -0,0 +1 @@
pub mod service;

View File

@ -0,0 +1 @@

View File

@ -1,630 +0,0 @@
//
//! feat mod 里的函数主要用于
//! - hotkey 快捷键
//! - timer 定时器
//! - cmds 页面调用
//!
use crate::cmds;
use crate::config::*;
use crate::core::*;
use crate::log_err;
use crate::utils::dirs::app_home_dir;
use crate::utils::resolve;
use anyhow::{bail, Result};
use reqwest_dav::list_cmd::ListFile;
use serde_yaml::{Mapping, Value};
use std::fs;
use tauri::Manager;
use tauri_plugin_clipboard_manager::ClipboardExt;
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
use std::env;
// 打开面板
#[allow(dead_code)]
pub fn open_or_close_dashboard() {
println!("Attempting to open/close dashboard");
log::info!(target: "app", "Attempting to open/close dashboard");
if let Some(window) = handle::Handle::global().get_window() {
println!("Found existing window");
log::info!(target: "app", "Found existing window");
// 如果窗口存在,则切换其显示状态
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();
}
}
// 重启clash
pub fn restart_clash_core() {
tauri::async_runtime::spawn(async {
match CoreManager::global().restart_core().await {
Ok(_) => {
handle::Handle::refresh_clash();
handle::Handle::notice_message("set_config::ok", "ok");
}
Err(err) => {
handle::Handle::notice_message("set_config::error", format!("{err}"));
log::error!(target:"app", "{err}");
}
}
});
}
pub fn restart_app() {
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());
});
}
/// 设置窗口状态监控,实时保存窗口位置和大小
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());
},
_ => {}
}
});
}
// 切换模式 rule/global/direct/script mode
pub fn change_clash_mode(mode: String) {
let mut mapping = Mapping::new();
mapping.insert(Value::from("mode"), mode.clone().into());
tauri::async_runtime::spawn(async move {
log::debug!(target: "app", "change clash mode to {mode}");
match clash_api::patch_configs(&mapping).await {
Ok(_) => {
// 更新订阅
Config::clash().data().patch_config(mapping);
if Config::clash().data().save_config().is_ok() {
handle::Handle::refresh_clash();
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
}
}
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 切换系统代理
pub fn toggle_system_proxy() {
let enable = Config::verge().draft().enable_system_proxy;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
match patch_verge(IVerge {
enable_system_proxy: Some(!enable),
..IVerge::default()
}, false)
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 切换代理文件
pub fn toggle_proxy_profile(profile_index: String) {
tauri::async_runtime::spawn(async move {
let app_handle = handle::Handle::global().app_handle().unwrap();
match cmds::patch_profiles_config_by_profile_index(app_handle, profile_index).await {
Ok(_) => {
let _ = tray::Tray::global().update_menu();
}
Err(err) => {
log::error!(target: "app", "{err}");
}
}
});
}
// 切换tun模式
pub fn toggle_tun_mode(not_save_file: Option<bool>) {
let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
match patch_verge(IVerge {
enable_tun_mode: Some(!enable),
..IVerge::default()
}, not_save_file.unwrap_or(false))
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
pub fn quit(code: Option<i32>) {
let app_handle = handle::Handle::global().app_handle().unwrap();
handle::Handle::global().set_is_exiting();
toggle_tun_mode(Some(true));
resolve::resolve_reset();
log_err!(handle::Handle::global().get_window().unwrap().close());
app_handle.exit(code.unwrap_or(0));
}
/// 修改clash的订阅
pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::clash().draft().patch_config(patch.clone());
let res = {
// 激活订阅
if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
Config::generate().await?;
CoreManager::global().restart_core().await?;
} else {
if patch.get("mode").is_some() {
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?;
}
handle::Handle::refresh_clash();
<Result<()>>::Ok(())
};
match res {
Ok(()) => {
Config::clash().apply();
Config::clash().data().save_config()?;
Ok(())
}
Err(err) => {
Config::clash().discard();
Err(err)
}
}
}
/// 修改verge的订阅
/// 一般都是一个个的修改
pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
Config::verge().draft().patch_config(patch.clone());
let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy;
let pac = patch.proxy_auto_config;
let pac_content = patch.pac_file_content;
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"))]
let tray_icon: Option<String> = None;
let common_tray_icon = patch.common_tray_icon;
let sysproxy_tray_icon = patch.sysproxy_tray_icon;
let tun_tray_icon = patch.tun_tray_icon;
#[cfg(not(target_os = "windows"))]
let redir_enabled = patch.verge_redir_enabled;
#[cfg(not(target_os = "windows"))]
let redir_port = patch.verge_redir_port;
#[cfg(target_os = "linux")]
let tproxy_enabled = patch.verge_tproxy_enabled;
#[cfg(target_os = "linux")]
let tproxy_port = patch.verge_tproxy_port;
let socks_enabled = patch.verge_socks_enabled;
let socks_port = patch.verge_socks_port;
let http_enabled = patch.verge_http_enabled;
let http_port = patch.verge_port;
let enable_tray_speed = patch.enable_tray_speed;
let enable_global_hotkey = patch.enable_global_hotkey;
let res: std::result::Result<(), anyhow::Error> = {
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() {
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() {
should_update_verge_config = true;
}
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
should_restart_core = true;
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
should_restart_core = true;
}
if socks_enabled.is_some()
|| http_enabled.is_some()
|| socks_port.is_some()
|| http_port.is_some()
|| mixed_port.is_some()
{
should_restart_core = true;
}
if auto_launch.is_some() {
should_update_launch = true;
}
if system_proxy.is_some() {
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() {
should_update_sysproxy = true;
}
if language.is_some() {
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()
{
should_update_systray_icon = true;
}
if patch.hotkeys.is_some() {
should_update_hotkey = true;
should_update_systray_menu = true;
}
if enable_tray_speed.is_some() {
should_update_systray_icon = true;
}
if should_restart_core {
CoreManager::global().restart_core().await?;
}
if should_update_clash_config {
CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
}
if should_update_verge_config {
Config::verge().draft().enable_global_hotkey = enable_global_hotkey;
handle::Handle::refresh_verge();
}
if should_update_launch {
sysopt::Sysopt::global().update_launch()?;
}
if should_update_sysproxy {
sysopt::Sysopt::global().update_sysproxy().await?;
}
if should_update_hotkey {
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
}
if should_update_systray_menu {
tray::Tray::global().update_menu()?;
}
if should_update_systray_icon {
tray::Tray::global().update_icon(None)?;
}
if should_update_systray_tooltip {
tray::Tray::global().update_tooltip()?;
}
// 处理轻量模式切换
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(); // 重新创建窗口
}
}
}
<Result<()>>::Ok(())
};
match res {
Ok(()) => {
Config::verge().apply();
if !not_save_file {
Config::verge().data().save_file()?;
}
Ok(())
}
Err(err) => {
Config::verge().discard();
Err(err)
}
}
}
/// 更新某个profile
/// 如果更新当前订阅就激活订阅
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().map_or(false, |s| s == "remote");
if !is_remote {
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);
None // 非远程订阅直接更新
} else if item.url.is_none() {
println!("[订阅更新] {} 缺少URL无法更新", uid);
bail!("failed to get the profile item url");
} else {
println!("[订阅更新] {} 是远程订阅URL: {}", uid, item.url.clone().unwrap());
Some((item.url.clone().unwrap(), item.option.clone()))
}
};
let should_update = match url_opt {
Some((url, opt)) => {
println!("[订阅更新] 开始下载新的订阅内容");
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
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();
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current
}
None => true,
};
if should_update {
println!("[订阅更新] 更新内核配置");
match CoreManager::global().update_config().await {
Ok(_) => {
println!("[订阅更新] 更新成功");
handle::Handle::refresh_clash();
}
Err(err) => {
println!("[订阅更新] 更新失败: {}", err);
handle::Handle::notice_message("set_config::error", format!("{err}"));
log::error!(target: "app", "{err}");
}
}
}
Ok(())
}
/// copy env variable
pub fn copy_clash_env() {
// 从环境变量获取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) };
let http_proxy = format!("http://{clash_verge_rev_ip}:{}", port);
let socks5_proxy = format!("socks5://{clash_verge_rev_ip}:{}", port);
let sh =
format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
let nu: String =
format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}");
let cliboard = app_handle.clipboard();
let env_type = { Config::verge().latest().env_type.clone() };
let env_type = match env_type {
Some(env_type) => env_type,
None => {
#[cfg(not(target_os = "windows"))]
let default = "bash";
#[cfg(target_os = "windows")]
let default = "powershell";
default.to_string()
}
};
match env_type.as_str() {
"bash" => cliboard.write_text(sh).unwrap_or_default(),
"cmd" => cliboard.write_text(cmd).unwrap_or_default(),
"powershell" => cliboard.write_text(ps).unwrap_or_default(),
"nushell" => cliboard.write_text(nu).unwrap_or_default(),
_ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
};
}
pub async fn test_delay(url: String) -> Result<u32> {
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);
let proxy_scheme = format!("http://127.0.0.1:{port}");
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 = request.send().await;
match response {
Ok(response) => {
log::trace!(target: "app", "test_delay response: {:#?}", response);
if response.status().is_success() {
Ok(start.elapsed().as_millis() as u32)
} else {
Ok(10000u32)
}
}
Err(err) => {
log::trace!(target: "app", "test_delay error: {:#?}", err);
Err(err.into())
}
}
}
pub async fn create_backup_and_upload_webdav() -> Result<()> {
let (file_name, temp_file_path) = backup::create_backup().map_err(|err| {
log::error!(target: "app", "Failed to create backup: {:#?}", err);
err
})?;
if let Err(err) = backup::WebDavClient::global()
.upload(temp_file_path.clone(), file_name)
.await
{
log::error!(target: "app", "Failed to upload to WebDAV: {:#?}", err);
return Err(err);
}
if let Err(err) = std::fs::remove_file(&temp_file_path) {
log::warn!(target: "app", "Failed to remove temp file: {:#?}", err);
}
Ok(())
}
pub async fn list_wevdav_backup() -> Result<Vec<ListFile>> {
backup::WebDavClient::global().list().await.map_err(|err| {
log::error!(target: "app", "Failed to list WebDAV backup files: {:#?}", err);
err
})
}
pub async fn delete_webdav_backup(filename: String) -> Result<()> {
backup::WebDavClient::global()
.delete(filename)
.await
.map_err(|err| {
log::error!(target: "app", "Failed to delete WebDAV backup file: {:#?}", err);
err
})
}
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
let verge = Config::verge();
let verge_data = verge.data().clone();
let webdav_url = verge_data.webdav_url.clone();
let webdav_username = verge_data.webdav_username.clone();
let webdav_password = verge_data.webdav_password.clone();
let backup_storage_path = app_home_dir().unwrap().join(&filename);
backup::WebDavClient::global()
.download(filename, backup_storage_path.clone())
.await
.map_err(|err| {
log::error!(target: "app", "Failed to download WebDAV backup file: {:#?}", err);
err
})?;
// extract zip file
let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?;
zip.extract(app_home_dir()?)?;
log_err!(
patch_verge(IVerge {
webdav_url,
webdav_username,
webdav_password,
..IVerge::default()
},
false)
.await
);
// 最后删除临时文件
fs::remove_file(backup_storage_path)?;
Ok(())
}

View File

@ -0,0 +1,89 @@
use crate::{
config::{Config, IVerge},
core::backup,
logging_error,
utils::{dirs::app_home_dir, logging::Type},
};
use anyhow::Result;
use reqwest_dav::list_cmd::ListFile;
use std::fs;
/// Create a backup and upload to WebDAV
pub async fn create_backup_and_upload_webdav() -> Result<()> {
let (file_name, temp_file_path) = backup::create_backup().map_err(|err| {
log::error!(target: "app", "Failed to create backup: {:#?}", err);
err
})?;
if let Err(err) = backup::WebDavClient::global()
.upload(temp_file_path.clone(), file_name)
.await
{
log::error!(target: "app", "Failed to upload to WebDAV: {:#?}", err);
return Err(err);
}
if let Err(err) = std::fs::remove_file(&temp_file_path) {
log::warn!(target: "app", "Failed to remove temp file: {:#?}", err);
}
Ok(())
}
/// List WebDAV backups
pub async fn list_wevdav_backup() -> Result<Vec<ListFile>> {
backup::WebDavClient::global().list().await.map_err(|err| {
log::error!(target: "app", "Failed to list WebDAV backup files: {:#?}", err);
err
})
}
/// Delete WebDAV backup
pub async fn delete_webdav_backup(filename: String) -> Result<()> {
backup::WebDavClient::global()
.delete(filename)
.await
.map_err(|err| {
log::error!(target: "app", "Failed to delete WebDAV backup file: {:#?}", err);
err
})
}
/// Restore WebDAV backup
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
let verge = Config::verge();
let verge_data = verge.data().clone();
let webdav_url = verge_data.webdav_url.clone();
let webdav_username = verge_data.webdav_username.clone();
let webdav_password = verge_data.webdav_password.clone();
let backup_storage_path = app_home_dir().unwrap().join(&filename);
backup::WebDavClient::global()
.download(filename, backup_storage_path.clone())
.await
.map_err(|err| {
log::error!(target: "app", "Failed to download WebDAV backup file: {:#?}", err);
err
})?;
// 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,
super::patch_verge(
IVerge {
webdav_url,
webdav_username,
webdav_password,
..IVerge::default()
},
false
)
.await
);
// 最后删除临时文件
fs::remove_file(backup_storage_path)?;
Ok(())
}

139
src-tauri/src/feat/clash.rs Normal file
View File

@ -0,0 +1,139 @@
use crate::{
config::Config,
core::{handle, tray, CoreManager},
logging_error,
module::mihomo::MihomoManager,
utils::{logging::Type, resolve},
};
use serde_yaml::{Mapping, Value};
use tauri::Manager;
/// Restart the Clash core
pub fn restart_clash_core() {
tauri::async_runtime::spawn(async {
match CoreManager::global().restart_core().await {
Ok(_) => {
handle::Handle::refresh_clash();
handle::Handle::notice_message("set_config::ok", "ok");
}
Err(err) => {
handle::Handle::notice_message("set_config::error", format!("{err}"));
log::error!(target:"app", "{err}");
}
}
});
}
/// Restart the application
pub fn restart_app() {
tauri::async_runtime::spawn_blocking(|| {
tauri::async_runtime::block_on(async {
logging_error!(Type::Core, true, CoreManager::global().stop_core().await);
resolve::resolve_reset_async().await;
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() {
tauri::async_runtime::spawn(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();
mapping.insert(Value::from("mode"), mode.clone().into());
// Convert YAML mapping to JSON Value
let json_value = serde_json::json!({
"mode": mode
});
tauri::async_runtime::spawn(async move {
log::debug!(target: "app", "change clash mode to {mode}");
match MihomoManager::global().patch_configs(json_value).await {
Ok(_) => {
// 更新订阅
Config::clash().data().patch_config(mapping);
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();
}
}
Err(err) => println!("{err}"),
}
});
}
/// Test connection delay to a URL
pub async fn test_delay(url: String) -> anyhow::Result<u32> {
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);
let proxy_scheme = format!("http://127.0.0.1:{port}");
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 = request.send().await;
match response {
Ok(response) => {
log::trace!(target: "app", "test_delay response: {:#?}", response);
if response.status().is_success() {
Ok(start.elapsed().as_millis() as u32)
} else {
Ok(10000u32)
}
}
Err(err) => {
log::trace!(target: "app", "test_delay error: {:#?}", err);
Err(err.into())
}
}
}

View File

@ -0,0 +1,226 @@
use crate::{
config::{Config, IVerge},
core::{handle, hotkey, sysopt, tray, CoreManager},
logging_error,
module::lightweight,
utils::logging::Type,
};
use anyhow::Result;
use serde_yaml::Mapping;
/// Patch Clash configuration
pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::clash().draft().patch_config(patch.clone());
let res = {
// 激活订阅
if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
Config::generate().await?;
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));
}
Config::runtime().latest().patch_config(patch);
CoreManager::global().update_config().await?;
}
handle::Handle::refresh_clash();
<Result<()>>::Ok(())
};
match res {
Ok(()) => {
Config::clash().apply();
Config::clash().data().save_config()?;
Ok(())
}
Err(err) => {
Config::clash().discard();
Err(err)
}
}
}
// 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());
let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy;
let pac = patch.proxy_auto_config;
let pac_content = patch.pac_file_content;
let proxy_bypass = patch.system_proxy_bypass;
let language = patch.language;
let mixed_port = patch.verge_mixed_port;
#[cfg(target_os = "macos")]
let tray_icon = patch.tray_icon;
#[cfg(not(target_os = "macos"))]
let tray_icon: Option<String> = None;
let common_tray_icon = patch.common_tray_icon;
let sysproxy_tray_icon = patch.sysproxy_tray_icon;
let tun_tray_icon = patch.tun_tray_icon;
#[cfg(not(target_os = "windows"))]
let redir_enabled = patch.verge_redir_enabled;
#[cfg(not(target_os = "windows"))]
let redir_port = patch.verge_redir_port;
#[cfg(target_os = "linux")]
let tproxy_enabled = patch.verge_tproxy_enabled;
#[cfg(target_os = "linux")]
let tproxy_port = patch.verge_tproxy_port;
let socks_enabled = patch.verge_socks_enabled;
let socks_port = patch.verge_socks_port;
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;
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;
}
if enable_global_hotkey.is_some() || home_cards.is_some() {
update_flags |= UpdateFlags::VergeConfig as i32;
}
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
if socks_enabled.is_some()
|| http_enabled.is_some()
|| socks_port.is_some()
|| http_port.is_some()
|| mixed_port.is_some()
{
update_flags |= UpdateFlags::RestartCore as i32;
}
if auto_launch.is_some() {
update_flags |= UpdateFlags::Launch as i32;
}
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;
}
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
}
if language.is_some() {
update_flags |= UpdateFlags::SystrayMenu as i32;
}
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;
}
if patch.hotkeys.is_some() {
update_flags |= UpdateFlags::Hotkey as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
}
if tray_event.is_some() {
update_flags |= UpdateFlags::SystrayClickBehavior as i32;
}
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?;
CoreManager::global().restart_core().await?;
}
if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 {
CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
}
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
Config::verge().draft().enable_global_hotkey = enable_global_hotkey;
handle::Handle::refresh_verge();
}
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
sysopt::Sysopt::global().update_launch()?;
}
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
sysopt::Sysopt::global().update_sysproxy().await?;
}
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0 {
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
}
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
tray::Tray::global().update_menu()?;
}
if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 {
tray::Tray::global().update_icon(None)?;
}
if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 {
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();
}
}
<Result<()>>::Ok(())
};
match res {
Ok(()) => {
Config::verge().apply();
if !not_save_file {
Config::verge().data().save_file()?;
}
Ok(())
}
Err(err) => {
Config::verge().discard();
Err(err)
}
}
}

14
src-tauri/src/feat/mod.rs Normal file
View File

@ -0,0 +1,14 @@
mod backup;
mod clash;
mod config;
mod profile;
mod proxy;
mod window;
// Re-export all functions from modules
pub use backup::*;
pub use clash::*;
pub use config::*;
pub use profile::*;
pub use proxy::*;
pub use window::*;

View File

@ -0,0 +1,92 @@
use crate::{
cmd,
config::{Config, PrfItem, PrfOption},
core::{handle, CoreManager, *},
};
use anyhow::{bail, Result};
/// Toggle proxy profile
pub fn toggle_proxy_profile(profile_index: String) {
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(_) => {
let _ = tray::Tray::global().update_menu();
}
Err(err) => {
log::error!(target: "app", "{err}");
}
}
});
}
/// Update a profile
/// If updating current profile, activate it
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");
if !is_remote {
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);
None // 非远程订阅直接更新
} else if item.url.is_none() {
println!("[订阅更新] {} 缺少URL无法更新", uid);
bail!("failed to get the profile item url");
} else {
println!(
"[订阅更新] {} 是远程订阅URL: {}",
uid,
item.url.clone().unwrap()
);
Some((item.url.clone().unwrap(), item.option.clone()))
}
};
let should_update = match url_opt {
Some((url, opt)) => {
println!("[订阅更新] 开始下载新的订阅内容");
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
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();
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current
}
None => true,
};
if should_update {
println!("[订阅更新] 更新内核配置");
match CoreManager::global().update_config().await {
Ok(_) => {
println!("[订阅更新] 更新成功");
handle::Handle::refresh_clash();
}
Err(err) => {
println!("[订阅更新] 更新失败: {}", err);
handle::Handle::notice_message("set_config::error", format!("{err}"));
log::error!(target: "app", "{err}");
}
}
}
Ok(())
}
/// 增强配置
pub async fn enhance_profiles() -> Result<()> {
crate::core::CoreManager::global()
.update_config()
.await
.map(|_| ())
}

109
src-tauri/src/feat/proxy.rs Normal file
View File

@ -0,0 +1,109 @@
use crate::{
config::{Config, IVerge},
core::handle,
};
use std::env;
use tauri_plugin_clipboard_manager::ClipboardExt;
/// Toggle system proxy on/off
pub fn toggle_system_proxy() {
let enable = Config::verge().draft().enable_system_proxy;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
match super::patch_verge(
IVerge {
enable_system_proxy: Some(!enable),
..IVerge::default()
},
false,
)
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
/// Toggle TUN mode on/off
pub fn toggle_tun_mode(not_save_file: Option<bool>) {
// tauri::async_runtime::spawn(async move {
// logging!(
// info,
// Type::Service,
// true,
// "Toggle TUN mode need install service"
// );
// if is_service_available().await.is_err() {
// logging_error!(Type::Service, true, install_service().await);
// }
// logging_error!(Type::Core, true, CoreManager::global().restart_core().await);
// });
let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
match super::patch_verge(
IVerge {
enable_tun_mode: Some(!enable),
..IVerge::default()
},
not_save_file.unwrap_or(false),
)
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
/// Copy proxy environment variables to clipboard
pub fn copy_clash_env() {
// 从环境变量获取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) };
let http_proxy = format!("http://{clash_verge_rev_ip}:{}", port);
let socks5_proxy = format!("socks5://{clash_verge_rev_ip}:{}", port);
let cliboard = app_handle.clipboard();
let env_type = { Config::verge().latest().env_type.clone() };
let env_type = match env_type {
Some(env_type) => env_type,
None => {
#[cfg(not(target_os = "windows"))]
let default = "bash";
#[cfg(target_os = "windows")]
let default = "powershell";
default.to_string()
}
};
let export_text = match env_type.as_str() {
"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}\" }}")
}
"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}");
return;
}
};
if cliboard.write_text(export_text).is_err() {
log::error!(target: "app", "Failed to write to clipboard");
}
}

View File

@ -0,0 +1,119 @@
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::Config,
core::{handle, sysopt, CoreManager},
module::mihomo::MihomoManager,
utils::resolve,
};
/// Open or close the dashboard window
#[allow(dead_code)]
pub fn open_or_close_dashboard() {
println!("Attempting to open/close dashboard");
log::info!(target: "app", "Attempting to open/close dashboard");
if let Some(window) = handle::Handle::global().get_window() {
println!("Found existing window");
log::info!(target: "app", "Found existing window");
// 如果窗口存在,则切换其显示状态
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(true);
}
}
/// 优化的应用退出函数
pub fn quit(code: Option<i32>) {
log::debug!(target: "app", "启动退出流程");
// 获取应用句柄并设置退出标志
let app_handle = handle::Handle::global().app_handle().unwrap();
handle::Handle::global().set_is_exiting();
// 优先关闭窗口,提供立即反馈
if let Some(window) = handle::Handle::global().get_window() {
let _ = window.hide();
}
// 在单独线程中处理资源清理,避免阻塞主线程
std::thread::spawn(move || {
// 使用tokio运行时执行异步清理任务
tauri::async_runtime::block_on(async {
// 使用超时机制处理清理操作
use tokio::time::{timeout, Duration};
// 1. 直接关闭TUN模式 (优先处理,通常最容易卡住)
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
let disable = serde_json::json!({
"tun": {
"enable": false
}
});
// 设置1秒超时
let _ = timeout(
Duration::from_secs(1),
MihomoManager::global().patch_configs(disable),
)
.await;
}
// 2. 并行处理系统代理和核心进程清理
let proxy_future = timeout(
Duration::from_secs(1),
sysopt::Sysopt::global().reset_sysproxy(),
);
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));
});
}
#[cfg(target_os = "macos")]
pub fn hide() {
if let Some(window) = handle::Handle::global().get_window() {
if window.is_visible().unwrap_or(false) {
AppHandleManager::global().set_activation_policy_accessory();
let _ = window.hide();
}
}
}

View File

@ -1,15 +1,87 @@
mod cmds;
mod cmd;
mod config;
mod core;
mod enhance;
mod error;
mod feat;
mod module;
mod utils;
use crate::core::hotkey;
use crate::utils::{resolve, resolve::resolve_scheme, server};
use crate::{
core::hotkey,
utils::{resolve, resolve::resolve_scheme, server},
};
use config::Config;
use std::sync::{Mutex, Once};
use tauri::AppHandle;
#[cfg(target_os = "macos")]
use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use utils::logging::Type;
/// A global singleton handle to the application.
pub struct AppHandleManager {
inner: Mutex<Option<AppHandle>>,
init: Once,
}
impl AppHandleManager {
/// Get the global instance of the app handle manager.
pub fn global() -> &'static Self {
static INSTANCE: AppHandleManager = AppHandleManager {
inner: Mutex::new(None),
init: Once::new(),
};
&INSTANCE
}
/// Initialize the app handle manager with an app handle.
pub fn init(&self, handle: AppHandle) {
self.init.call_once(|| {
let mut app_handle = self.inner.lock().unwrap();
*app_handle = Some(handle);
});
}
/// Get the app handle if it has been initialized.
pub fn get(&self) -> Option<AppHandle> {
self.inner.lock().unwrap().clone()
}
/// Get the app handle, panics if it hasn't been initialized.
pub fn get_handle(&self) -> AppHandle {
self.get().expect("AppHandle not initialized")
}
pub fn set_activation_policy_regular(&self) {
#[cfg(target_os = "macos")]
{
let app_handle = self.inner.lock().unwrap();
let app_handle = app_handle.as_ref().unwrap();
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular);
}
}
pub fn set_activation_policy_accessory(&self) {
#[cfg(target_os = "macos")]
{
let app_handle = self.inner.lock().unwrap();
let app_handle = app_handle.as_ref().unwrap();
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory);
}
}
pub fn set_activation_policy_prohibited(&self) {
#[cfg(target_os = "macos")]
{
let app_handle = self.inner.lock().unwrap();
let app_handle = app_handle.as_ref().unwrap();
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Prohibited);
}
}
}
#[allow(clippy::panic)]
pub fn run() {
// 单例检测
let app_exists: bool = tauri::async_runtime::block_on(async move {
@ -29,7 +101,6 @@ pub fn run() {
#[cfg(debug_assertions)]
let devtools = tauri_plugin_devtools::init();
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(
@ -41,7 +112,6 @@ pub fn run() {
.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())
@ -51,13 +121,13 @@ pub fn run() {
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
{
use tauri_plugin_deep_link::DeepLinkExt;
log_err!(app.deep_link().register_all());
logging_error!(Type::System, true, app.deep_link().register_all());
}
app.deep_link().on_open_url(|event| {
tauri::async_runtime::spawn(async move {
if let Some(url) = event.urls().first() {
log_err!(resolve_scheme(url.to_string()).await);
logging_error!(Type::Setup, true, resolve_scheme(url.to_string()).await);
}
});
});
@ -70,61 +140,86 @@ pub fn run() {
})
.invoke_handler(tauri::generate_handler![
// common
cmds::get_sys_proxy,
cmds::get_auto_proxy,
cmds::open_app_dir,
cmds::open_logs_dir,
cmds::open_web_url,
cmds::open_core_dir,
cmds::get_portable_flag,
cmds::get_network_interfaces,
cmds::restart_core,
cmds::restart_app,
cmd::get_sys_proxy,
cmd::get_auto_proxy,
cmd::open_app_dir,
cmd::open_logs_dir,
cmd::open_web_url,
cmd::open_core_dir,
cmd::get_portable_flag,
cmd::get_network_interfaces,
cmd::restart_core,
cmd::restart_app,
// 添加新的命令
cmd::get_running_mode,
cmd::get_app_uptime,
cmd::get_auto_launch_status,
cmd::is_admin,
// service 管理
cmd::install_service,
cmd::uninstall_service,
cmd::reinstall_service,
cmd::repair_service,
// clash
cmds::get_clash_info,
cmds::patch_clash_config,
cmds::patch_clash_mode,
cmds::change_clash_core,
cmds::get_runtime_config,
cmds::get_runtime_yaml,
cmds::get_runtime_exists,
cmds::get_runtime_logs,
cmds::uwp::invoke_uwp_tool,
cmds::copy_clash_env,
cmd::get_clash_info,
cmd::patch_clash_config,
cmd::patch_clash_mode,
cmd::change_clash_core,
cmd::get_runtime_config,
cmd::get_runtime_yaml,
cmd::get_runtime_exists,
cmd::get_runtime_logs,
cmd::invoke_uwp_tool,
cmd::copy_clash_env,
cmd::get_proxies,
cmd::get_providers_proxies,
cmd::save_dns_config,
cmd::apply_dns_config,
cmd::check_dns_config_exists,
cmd::get_dns_config_content,
// verge
cmds::get_verge_config,
cmds::patch_verge_config,
cmds::test_delay,
cmds::get_app_dir,
cmds::copy_icon_file,
cmds::download_icon_cache,
cmds::open_devtools,
cmds::exit_app,
cmds::get_network_interfaces_info,
cmd::get_verge_config,
cmd::patch_verge_config,
cmd::test_delay,
cmd::get_app_dir,
cmd::copy_icon_file,
cmd::download_icon_cache,
cmd::open_devtools,
cmd::exit_app,
cmd::get_network_interfaces_info,
// profile
cmds::get_profiles,
cmds::enhance_profiles,
cmds::patch_profiles_config,
cmds::view_profile,
cmds::patch_profile,
cmds::create_profile,
cmds::import_profile,
cmds::reorder_profile,
cmds::update_profile,
cmds::delete_profile,
cmds::read_profile_file,
cmds::save_profile_file,
cmd::get_profiles,
cmd::enhance_profiles,
cmd::patch_profiles_config,
cmd::view_profile,
cmd::patch_profile,
cmd::create_profile,
cmd::import_profile,
cmd::reorder_profile,
cmd::update_profile,
cmd::delete_profile,
cmd::read_profile_file,
cmd::save_profile_file,
// script validation
cmds::script_validate_notice,
cmds::validate_script_file,
cmd::script_validate_notice,
cmd::validate_script_file,
// clash api
cmds::clash_api_get_proxy_delay,
cmd::clash_api_get_proxy_delay,
// backup
cmds::create_webdav_backup,
cmds::save_webdav_config,
cmds::list_webdav_backup,
cmds::delete_webdav_backup,
cmds::restore_webdav_backup,
cmd::create_webdav_backup,
cmd::save_webdav_config,
cmd::list_webdav_backup,
cmd::delete_webdav_backup,
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)]
@ -132,11 +227,39 @@ 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(|_, e| match e {
app.run(|app_handle, e| match e {
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
AppHandleManager::global().init(app_handle.clone());
#[cfg(target_os = "macos")]
{
if let Some(window) = AppHandleManager::global()
.get_handle()
.get_webview_window("main")
{
let _ = window.set_title("Clash Verge");
}
}
}
#[cfg(target_os = "macos")]
tauri::RunEvent::Reopen {
has_visible_windows,
..
} => {
if !has_visible_windows {
AppHandleManager::global().set_activation_policy_regular();
}
AppHandleManager::global().init(app_handle.clone());
}
tauri::RunEvent::ExitRequested { api, code, .. } => {
if code.is_none() {
api.prevent_exit();
@ -146,6 +269,8 @@ pub fn run() {
if label == "main" {
match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_accessory();
if core::handle::Handle::global().is_exiting() {
return;
}
@ -157,45 +282,90 @@ pub fn run() {
tauri::WindowEvent::Focused(true) => {
#[cfg(target_os = "macos")]
{
log_err!(hotkey::Hotkey::global().register("CMD+Q", "quit"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+Q", "quit")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+W", "hide")
);
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("Control+Q", "quit")
);
};
{
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().init())
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().init())
}
}
}
tauri::WindowEvent::Focused(false) => {
#[cfg(target_os = "macos")]
{
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("Control+Q")
);
};
{
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().reset())
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().reset())
}
}
}
tauri::WindowEvent::Destroyed => {
#[cfg(target_os = "macos")]
{
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("Control+Q")
);
};
}
_ => {}

View File

@ -0,0 +1,142 @@
use anyhow::{Context, Result};
use delay_timer::prelude::TaskBuilder;
use tauri::{Listener, Manager};
use crate::{
config::Config,
core::{handle, timer::Timer},
log_err, logging,
utils::logging::Type,
AppHandleManager,
};
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
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() {
if let Some(window) = handle::Handle::global().get_window() {
if window.is_visible().unwrap_or(false) {
let _ = window.hide();
}
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, "轻量模式已开启");
}
let _ = cancel_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,
true,
"监听到窗口获得焦点,取消轻量模式计时"
);
});
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 mut timer_map = Timer::global().timer_map.write();
let delay_timer = Timer::global().delay_timer.write();
let mut timer_count = Timer::global().timer_count.lock();
let task_id = *timer_count;
*timer_count += 1;
let once_by_minutes = Config::verge()
.latest()
.auto_light_weight_minutes
.unwrap_or(10);
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")?;
delay_timer
.add_task(task)
.context("failed to add timer task")?;
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

@ -0,0 +1,70 @@
use crate::config::Config;
use mihomo_api;
use once_cell::sync::{Lazy, OnceCell};
use std::sync::Mutex;
use tauri::http::{HeaderMap, HeaderValue};
#[cfg(target_os = "macos")]
use tokio_tungstenite::tungstenite::http;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Rate {
pub up: u64,
pub down: u64,
}
pub struct MihomoManager {
mihomo: Mutex<OnceCell<mihomo_api::MihomoManager>>,
}
impl MihomoManager {
fn __global() -> &'static MihomoManager {
static INSTANCE: Lazy<MihomoManager> = Lazy::new(|| MihomoManager {
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 lock = instance.mihomo.lock().unwrap();
if let Some(mihomo) = lock.get() {
if mihomo.get_mihomo_server() == current_server {
return mihomo.clone();
}
}
lock.set(mihomo_api::MihomoManager::new(current_server, headers))
.ok();
lock.get().unwrap().clone()
}
}
impl MihomoManager {
pub fn get_clash_client_info() -> Option<(String, HeaderMap)> {
let client = { Config::clash().data().get_client_info() };
let server = format!("http://{}", client.server);
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
if let Some(secret) = client.secret {
let secret = format!("Bearer {}", secret).parse().unwrap();
headers.insert("Authorization", secret);
}
Some((server, headers))
}
#[cfg(target_os = "macos")]
pub fn get_traffic_ws_url() -> (String, HeaderValue) {
let (url, headers) = MihomoManager::get_clash_client_info().unwrap();
let ws_url = url.replace("http://", "ws://") + "/traffic";
let auth = headers
.get("Authorization")
.unwrap()
.to_str()
.unwrap()
.to_string();
let token = http::header::HeaderValue::from_str(&auth).unwrap();
(ws_url, token)
}
}

View File

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

View File

@ -0,0 +1,64 @@
use crate::{
cmd::system,
core::{handle, CoreManager},
};
use std::fmt::{self, Debug, Formatter};
use sysinfo::System;
pub struct PlatformSpecification {
system_name: String,
system_version: String,
system_kernel_version: String,
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
)
}
}
impl PlatformSpecification {
pub fn new() -> Self {
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 handler = handle::Handle::global().app_handle().unwrap();
let config = handler.config();
let verge_version = config.version.clone().unwrap_or("Null".into());
// 使用默认值避免在同步上下文中执行异步操作
let running_mode = "NotRunning".to_string();
let is_admin = system::is_admin().unwrap_or_default();
Self {
system_name,
system_version,
system_kernel_version,
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,8 +1,7 @@
use crate::core::handle;
use anyhow::Result;
use once_cell::sync::OnceCell;
use std::fs;
use std::path::PathBuf;
use std::{fs, path::PathBuf};
use tauri::Manager;
#[cfg(not(feature = "verge-dev"))]
@ -78,6 +77,36 @@ 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"))
@ -137,7 +166,7 @@ pub fn get_encryption_key() -> Result<Vec<u8>> {
} else {
// Generate and save new key
let mut key = vec![0u8; 32];
getrandom::getrandom(&mut key)?;
getrandom::fill(&mut key)?;
// Ensure directory exists
if let Some(parent) = key_path.parent() {

View File

@ -1,8 +1,8 @@
use crate::enhance::seq::SeqMap;
use crate::{enhance::seq::SeqMap, logging, utils::logging::Type};
use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::{Mapping, Value};
use serde_yaml::Mapping;
use std::{fs, path::PathBuf, str::FromStr};
/// read data from yaml as struct T
@ -22,9 +22,18 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
})
}
/// read mapping from yaml fix #165
/// read mapping from yaml
pub fn read_mapping(path: &PathBuf) -> Result<Mapping> {
let mut val: Value = read_yaml(path)?;
if !path.exists() {
bail!("file not found \"{}\"", 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()))?;
@ -35,6 +44,19 @@ pub fn read_mapping(path: &PathBuf) -> Result<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)
}
}
}
/// read mapping from yaml fix #165
@ -136,52 +158,6 @@ 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 {
@ -205,22 +181,24 @@ macro_rules! t {
/// 支持 B/s、KB/s、MB/s、GB/s 的自动转换
///
/// # Examples
/// ```not_run
/// format_bytes_speed(1000) // returns "1000B/s"
/// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s"
/// ```
/// assert_eq!(format_bytes_speed(1000), "1000B/s");
/// assert_eq!(format_bytes_speed(1024), "1.0KB/s");
/// assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s");
/// ```
#[cfg(target_os = "macos")]
pub fn format_bytes_speed(speed: u64) -> String {
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)
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;
}
format!("{:.1}{}/s", size, UNITS[unit_index])
}
#[cfg(target_os = "macos")]

View File

@ -1,5 +1,4 @@
use crate::config::Config;
use crate::utils::dirs;
use crate::{config::Config, utils::dirs};
use once_cell::sync::Lazy;
use serde_json::Value;
use std::{collections::HashMap, fs, path::PathBuf};

View File

@ -1,16 +1,21 @@
use crate::config::*;
use crate::core::handle;
use crate::utils::{dirs, help};
use crate::{
config::*,
core::handle,
utils::{dirs, help},
};
use anyhow::Result;
use chrono::{Local, TimeZone};
use log::LevelFilter;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Logger, Root};
use log4rs::encode::pattern::PatternEncoder;
use std::fs::{self, DirEntry};
use std::path::PathBuf;
use std::str::FromStr;
use log4rs::{
append::{console::ConsoleAppender, file::FileAppender},
config::{Appender, Logger, Root},
encode::pattern::PatternEncoder,
};
use std::{
fs::{self, DirEntry},
path::PathBuf,
str::FromStr,
};
use tauri_plugin_shell::ShellExt;
/// initialize this instance's log file
@ -133,6 +138,113 @@ pub fn delete_log() -> Result<()> {
Ok(())
}
/// 初始化DNS配置文件
fn init_dns_config() -> Result<()> {
use serde_yaml::Value;
// 获取默认DNS配置
let default_dns_config = serde_yaml::Mapping::from_iter([
("enable".into(), Value::Bool(true)),
("listen".into(), Value::String(":53".into())),
("enhanced-mode".into(), Value::String("fake-ip".into())),
(
"fake-ip-range".into(),
Value::String("198.18.0.1/16".into()),
),
(
"fake-ip-filter-mode".into(),
Value::String("blacklist".into()),
),
("prefer-h3".into(), Value::Bool(false)),
("respect-rules".into(), Value::Bool(false)),
("use-hosts".into(), Value::Bool(false)),
("use-system-hosts".into(), Value::Bool(false)),
(
"fake-ip-filter".into(),
Value::Sequence(vec![
Value::String("*.lan".into()),
Value::String("*.local".into()),
Value::String("*.arpa".into()),
Value::String("time.*.com".into()),
Value::String("ntp.*.com".into()),
Value::String("time.*.com".into()),
Value::String("+.market.xiaomi.com".into()),
Value::String("localhost.ptlogin2.qq.com".into()),
Value::String("*.msftncsi.com".into()),
Value::String("www.msftconnecttest.com".into()),
]),
),
(
"default-nameserver".into(),
Value::Sequence(vec![
Value::String("system".into()),
Value::String("223.6.6.6".into()),
Value::String("8.8.8.8".into()),
]),
),
(
"nameserver".into(),
Value::Sequence(vec![
Value::String("8.8.8.8".into()),
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
]),
),
("fallback".into(), Value::Sequence(vec![])),
(
"nameserver-policy".into(),
Value::Mapping(serde_yaml::Mapping::new()),
),
(
"proxy-server-nameserver".into(),
Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("tls://223.5.5.5".into()),
]),
),
("direct-nameserver".into(), Value::Sequence(vec![])),
("direct-nameserver-follow-policy".into(), Value::Bool(false)),
(
"fallback-filter".into(),
Value::Mapping(serde_yaml::Mapping::from_iter([
("geoip".into(), Value::Bool(true)),
("geoip-code".into(), Value::String("CN".into())),
(
"ipcidr".into(),
Value::Sequence(vec![
Value::String("240.0.0.0/4".into()),
Value::String("0.0.0.0/32".into()),
]),
),
(
"domain".into(),
Value::Sequence(vec![
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
]),
),
])),
),
]);
// 检查DNS配置文件是否存在
let app_dir = dirs::app_home_dir()?;
let dns_path = app_dir.join("dns_config.yaml");
if !dns_path.exists() {
log::info!(target: "app", "Creating default DNS config file");
help::save_yaml(
&dns_path,
&default_dns_config,
Some("# Clash Verge DNS Config"),
)?;
}
Ok(())
}
/// Initialize all the config files
/// before tauri setup
pub fn init_config() -> Result<()> {
@ -173,6 +285,9 @@ pub fn init_config() -> Result<()> {
<Result<()>>::Ok(())
}));
// 初始化DNS配置文件
let _ = init_dns_config();
Ok(())
}
@ -180,15 +295,11 @@ pub fn init_config() -> Result<()> {
/// after tauri setup
pub fn init_resources() -> Result<()> {
let app_dir = dirs::app_home_dir()?;
let test_dir = app_dir.join("test");
let res_dir = dirs::app_resources_dir()?;
if !app_dir.exists() {
let _ = fs::create_dir_all(&app_dir);
}
if !test_dir.exists() {
let _ = fs::create_dir_all(&test_dir);
}
if !res_dir.exists() {
let _ = fs::create_dir_all(&res_dir);
}
@ -200,7 +311,6 @@ pub fn init_resources() -> Result<()> {
for file in file_list.iter() {
let src_path = res_dir.join(file);
let dest_path = app_dir.join(file);
let test_dest_path = test_dir.join(file);
log::debug!(target: "app", "src_path: {src_path:?}, dest_path: {dest_path:?}");
let handle_copy = |dest: &PathBuf| {
@ -212,9 +322,6 @@ pub fn init_resources() -> Result<()> {
};
};
if src_path.exists() && !test_dest_path.exists() {
handle_copy(&test_dest_path);
}
if src_path.exists() && !dest_path.exists() {
handle_copy(&dest_path);
continue;
@ -245,8 +352,7 @@ pub fn init_resources() -> Result<()> {
#[cfg(target_os = "windows")]
pub fn init_scheme() -> Result<()> {
use tauri::utils::platform::current_exe;
use winreg::enums::*;
use winreg::RegKey;
use winreg::{enums::*, RegKey};
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;

View File

@ -0,0 +1,139 @@
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Type {
Cmd,
Core,
Config,
Setup,
System,
Service,
Hotkey,
Window,
Tray,
Timer,
Frontend,
Backup,
Lightweight,
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Cmd => write!(f, "[Cmd]"),
Type::Core => write!(f, "[Core]"),
Type::Config => write!(f, "[Config]"),
Type::Setup => write!(f, "[Setup]"),
Type::System => write!(f, "[System]"),
Type::Service => write!(f, "[Service]"),
Type::Hotkey => write!(f, "[Hotkey]"),
Type::Window => write!(f, "[Window]"),
Type::Tray => write!(f, "[Tray]"),
Type::Timer => write!(f, "[Timer]"),
Type::Frontend => write!(f, "[Frontend]"),
Type::Backup => write!(f, "[Backup]"),
Type::Lightweight => write!(f, "[Lightweight]"),
}
}
}
#[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()))
}
}
};
}
#[macro_export]
macro_rules! logging {
// 带 println 的版本(支持格式化参数)
($level:ident, $type:expr, true, $($arg:tt)*) => {
println!("{} {}", $type, format_args!($($arg)*));
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*));
};
// 带 println 的版本(使用 false 明确不打印)
($level:ident, $type:expr, false, $($arg:tt)*) => {
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*));
};
// 不带 print 参数的版本(默认不打印)
($level:ident, $type:expr, $($arg:tt)*) => {
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*));
};
}
#[macro_export]
macro_rules! logging_error {
// 1. 处理 Result<T, E>,带打印控制
($type:expr, $print:expr, $expr:expr) => {
match $expr {
Ok(_) => {},
Err(err) => {
if $print {
println!("[{}] Error: {}", $type, err);
}
log::error!(target: "app", "[{}] {}", $type, err);
}
}
};
// 2. 处理 Result<T, E>,默认不打印
($type:expr, $expr:expr) => {
if let Err(err) = $expr {
log::error!(target: "app", "[{}] {}", $type, err);
}
};
// 3. 处理格式化字符串,带打印控制
($type:expr, $print:expr, $fmt:literal $(, $arg:expr)*) => {
if $print {
println!("[{}] {}", $type, format_args!($fmt $(, $arg)*));
}
log::error!(target: "app", "[{}] {}", $type, format_args!($fmt $(, $arg)*));
};
// 4. 处理格式化字符串,不带 bool 时,默认 `false`
($type:expr, $fmt:literal $(, $arg:expr)*) => {
logging_error!($type, false, $fmt $(, $arg)*);
};
}

View File

@ -1,8 +1,9 @@
pub mod dirs;
pub mod error;
pub mod help;
pub mod i18n;
pub mod init;
pub mod logging;
pub mod resolve;
pub mod server;
pub mod tmpl;
pub mod i18n;

View File

@ -1,7 +1,13 @@
use crate::config::IVerge;
use crate::utils::error;
use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server};
use crate::{log_err, wrap_err};
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::{Config, IVerge, PrfItem},
core::*,
logging, logging_error,
module::lightweight,
utils::{error, init, logging::Type, server},
wrap_err,
};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use percent_encoding::percent_decode_str;
@ -9,10 +15,9 @@ use serde_yaml::Mapping;
use std::net::TcpListener;
use tauri::{App, Manager};
use url::Url;
use tauri::Url;
//#[cfg(not(target_os = "linux"))]
// use window_shadows::set_shadow;
use tauri_plugin_notification::NotificationExt;
pub static VERSION: OnceCell<String> = OnceCell::new();
@ -37,107 +42,107 @@ pub fn find_unused_port() -> Result<u16> {
pub async fn resolve_setup(app: &mut App) {
error::redirect_panic_to_log();
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
{
AppHandleManager::global().init(app.app_handle().clone());
AppHandleManager::global().set_activation_policy_accessory();
}
let version = app.package_info().version.to_string();
handle::Handle::global().init(app.app_handle());
VERSION.get_or_init(|| version.clone());
log_err!(init::init_config());
log_err!(init::init_resources());
log_err!(init::init_scheme());
log_err!(init::startup_script().await);
logging_error!(Type::Config, true, init::init_config());
logging_error!(Type::Setup, true, init::init_resources());
logging_error!(Type::Setup, true, init::init_scheme());
logging_error!(Type::Setup, true, init::startup_script().await);
// 处理随机端口
log_err!(resolve_random_port_config());
logging_error!(Type::System, true, resolve_random_port_config());
// 启动核心
log::trace!(target:"app", "init config");
log_err!(Config::init_config().await);
logging!(trace, Type::Config, true, "Initial config");
logging_error!(Type::Config, true, Config::init_config().await);
if service::check_service().await.is_err() {
match service::reinstall_service().await {
Ok(_) => {
log::info!(target:"app", "install service susccess.");
#[cfg(not(target_os = "macos"))]
std::thread::sleep(std::time::Duration::from_millis(1000));
#[cfg(target_os = "macos")]
{
let mut service_runing = false;
for _ in 0..40 {
if service::check_service().await.is_ok() {
service_runing = true;
break;
} else {
log::warn!(target: "app", "service not runing, sleep 500ms and check again.");
std::thread::sleep(std::time::Duration::from_millis(500));
}
}
if !service_runing {
log::error!(target: "app", "service not runing. exit");
app.app_handle().exit(-2);
}
}
}
Err(e) => {
log::error!(target: "app", "{e:?}");
app.app_handle().exit(-1);
}
}
}
log::trace!(target: "app", "launch core");
log_err!(CoreManager::global().init().await);
logging!(trace, Type::Core, "Starting CoreManager");
logging_error!(Type::Core, true, CoreManager::global().init().await);
// setup a simple http server for singleton
log::trace!(target: "app", "launch embed server");
server::embed_server();
log::trace!(target: "app", "init system tray");
log_err!(tray::Tray::global().init());
log_err!(tray::Tray::global().create_systray());
log::trace!(target: "app", "Initial system tray");
logging_error!(Type::Tray, true, tray::Tray::global().init());
logging_error!(Type::Tray, true, tray::Tray::global().create_systray(app));
log_err!(sysopt::Sysopt::global().update_sysproxy().await);
log_err!(sysopt::Sysopt::global().init_guard_sysproxy());
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().update_sysproxy().await
);
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().init_guard_sysproxy()
);
// 初始化热键
log::trace!(target: "app", "init hotkeys");
log_err!(hotkey::Hotkey::global().init());
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
create_window(!is_silent_start);
let silent_start = { Config::verge().data().enable_silent_start };
if !silent_start.unwrap_or(false) {
create_window();
logging_error!(Type::System, true, timer::Timer::global().init());
let enable_auto_light_weight_mode = { Config::verge().data().enable_auto_light_weight_mode };
if enable_auto_light_weight_mode.unwrap_or(false) {
lightweight::enable_auto_light_weight_mode();
}
log_err!(tray::Tray::global().update_part());
log_err!(timer::Timer::global().init());
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
// 初始化热键
logging!(trace, Type::System, true, "Initial hotkeys");
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
}
/// reset system proxy
pub fn resolve_reset() {
tauri::async_runtime::block_on(async move {
/// reset system proxy (异步版)
pub async fn resolve_reset_async() {
#[cfg(target_os = "macos")]
logging!(info, Type::Tray, true, "Unsubscribing from traffic updates");
#[cfg(target_os = "macos")]
tray::Tray::global().unsubscribe_traffic();
log_err!(sysopt::Sysopt::global().reset_sysproxy().await);
log_err!(CoreManager::global().stop_core().await);
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().reset_sysproxy().await
);
logging_error!(Type::Core, true, CoreManager::global().stop_core().await);
#[cfg(target_os = "macos")]
{
logging!(info, Type::System, true, "Restoring system DNS settings");
restore_public_dns().await;
});
}
}
/// create main window
pub fn create_window() {
println!("Starting to create window");
log::info!(target: "app", "Starting to create window");
pub fn create_window(is_showup: bool) {
logging!(info, Type::Window, true, "Creating window");
let app_handle = handle::Handle::global().app_handle().unwrap();
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_regular();
if let Some(window) = handle::Handle::global().get_window() {
println!("Found existing window, trying to show it");
log::info!(target: "app", "Found existing window, trying to show it");
logging!(
info,
Type::Window,
true,
"Found existing window, attempting to restore visibility"
);
if window.is_minimized().unwrap_or(false) {
println!("Window is minimized, unminimizing");
log::info!(target: "app", "Window is minimized, unminimizing");
logging!(
info,
Type::Window,
true,
"Window is minimized, restoring window state"
);
let _ = window.unminimize();
}
let _ = window.show();
@ -145,8 +150,7 @@ pub fn create_window() {
return;
}
println!("Creating new window");
log::info!(target: "app", "Creating new window");
logging!(info, Type::Window, true, "Creating new application window");
#[cfg(target_os = "windows")]
let window = tauri::WebviewWindowBuilder::new(
@ -192,17 +196,46 @@ pub fn create_window() {
match window {
Ok(window) => {
println!("Window created successfully, attempting to show");
log::info!(target: "app", "Window created successfully, attempting to show");
logging!(info, Type::Window, true, "Window created successfully");
if is_showup {
println!("is showup");
let _ = window.show();
let _ = window.set_focus();
} else {
let _ = window.hide();
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_accessory();
}
// 设置窗口状态监控,实时保存窗口位置和大小
crate::feat::setup_window_state_monitor(&app_handle);
// crate::feat::setup_window_state_monitor(&app_handle);
// 标记前端UI已准备就绪向前端发送启动完成事件
let app_handle_clone = app_handle.clone();
tauri::async_runtime::spawn(async move {
use tauri::Emitter;
logging!(
info,
Type::Window,
true,
"标记前端UI已准备就绪开始处理启动错误队列"
);
handle::Handle::global().mark_startup_completed();
if let Some(window) = app_handle_clone.get_webview_window("main") {
let _ = window.emit("verge://startup-completed", ());
}
});
}
Err(e) => {
println!("Failed to create window: {:?}", e);
log::error!(target: "app", "Failed to create window: {:?}", e);
logging!(
error,
Type::Window,
true,
"Failed to create window: {:?}",
e
);
}
}
}
@ -210,8 +243,6 @@ pub fn create_window() {
pub async fn resolve_scheme(param: String) -> Result<()> {
log::info!(target:"app", "received deep link: {}", param);
let app_handle = handle::Handle::global().app_handle().unwrap();
let param_str = if param.starts_with("[") && param.len() > 4 {
param
.get(2..param.len() - 2)
@ -234,41 +265,32 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
.find(|(key, _)| key == "name")
.map(|(_, value)| value.into_owned());
let encode_url = link_parsed
.query_pairs()
.find(|(key, _)| key == "url")
.map(|(_, value)| value.into_owned());
// 通过直接获取查询部分并解析特定参数来避免 URL 转义问题
let url_param = if let Some(query) = link_parsed.query() {
let prefix = "url=";
if let Some(pos) = query.find(prefix) {
let raw_url = &query[pos + prefix.len()..];
Some(percent_decode_str(raw_url).decode_utf8_lossy().to_string())
} else {
None
}
} else {
None
};
match encode_url {
match url_param {
Some(url) => {
let url = percent_decode_str(url.as_ref())
.decode_utf8_lossy()
.to_string();
log::info!(target:"app", "decoded subscription url: {}", url);
create_window();
create_window(false);
match PrfItem::from_url(url.as_ref(), name, None, None).await {
Ok(item) => {
let uid = item.uid.clone().unwrap();
let _ = wrap_err!(Config::profiles().data().append_item(item));
handle::Handle::notice_message("import_sub_url::ok", uid);
app_handle
.notification()
.builder()
.title("Clash Verge")
.body("Import profile success")
.show()
.unwrap();
}
Err(e) => {
handle::Handle::notice_message("import_sub_url::error", e.to_string());
app_handle
.notification()
.builder()
.title("Clash Verge")
.body(format!("Import profile failed: {e}"))
.show()
.unwrap();
}
}
}
@ -310,8 +332,7 @@ fn resolve_random_port_config() -> Result<()> {
#[cfg(target_os = "macos")]
pub async fn set_public_dns(dns_server: String) {
use crate::core::handle;
use crate::utils::dirs;
use crate::{core::handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap();
@ -347,8 +368,7 @@ pub async fn set_public_dns(dns_server: String) {
#[cfg(target_os = "macos")]
pub async fn restore_public_dns() {
use crate::core::handle;
use crate::utils::dirs;
use crate::{core::handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap();
log::info!(target: "app", "try to unset system dns");

View File

@ -1,8 +1,11 @@
extern crate warp;
use super::resolve;
use crate::config::{Config, IVerge, DEFAULT_PAC};
use crate::log_err;
use crate::{
config::{Config, IVerge, DEFAULT_PAC},
logging_error,
utils::logging::Type,
};
use anyhow::{bail, Result};
use port_scanner::local_port_available;
use std::convert::Infallible;
@ -46,7 +49,7 @@ pub fn embed_server() {
tauri::async_runtime::spawn(async move {
let visible = warp::path!("commands" / "visible").map(move || {
resolve::create_window();
resolve::create_window(false);
"ok"
});
@ -67,7 +70,11 @@ pub fn embed_server() {
.unwrap_or_default()
});
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
log_err!(resolve::resolve_scheme(query.param).await);
logging_error!(
Type::Setup,
true,
resolve::resolve_scheme(query.param).await
);
Ok("ok")
}

View File

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

View File

@ -0,0 +1,162 @@
use reqwest::{Method, 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,
}
}
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: Method,
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(60))
.build()
.map_err(|e| e.to_string())?
.request(method.clone(), &url)
.json(&data.unwrap_or(json!({})))
.send()
.await
.map_err(|e| e.to_string())?;
let response = match method {
Method::PATCH => {
let status = client_response.status();
if status.as_u16() == 204 {
json!({"code": 204})
} else {
client_response
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?
}
}
Method::PUT => json!(client_response.text().await.map_err(|e| e.to_string())?),
_ => client_response
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?,
};
Ok(response)
}
pub async fn refresh_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/proxies", self.mihomo_server);
let proxies = self.send_request(Method::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(Method::GET, url, None).await?;
self.update_providers_proxies(providers_proxies);
Ok(self)
}
}
impl MihomoManager {
pub async fn is_mihomo_running(&self) -> Result<(), String> {
let url = format!("{}/version", self.mihomo_server);
let _response = self.send_request(Method::GET, url, None).await?;
Ok(())
}
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(Method::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(Method::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(Method::GET, url, None).await?;
Ok(response)
}
pub async fn get_connections(&self) -> Result<serde_json::Value, String> {
let url = format!("{}/connections", self.mihomo_server);
let response = self.send_request(Method::GET, url, None).await?;
Ok(response)
}
pub async fn delete_connection(&self, id: &str) -> Result<(), String> {
let url = format!("{}/connections/{}", self.mihomo_server, id);
let response = self.send_request(Method::DELETE, url, None).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string())
}
}
}

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

@ -1,4 +1,5 @@
{
"version": "2.2.3",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,
@ -10,9 +11,15 @@
"icons/icon.icns",
"icons/icon.ico"
],
"resources": ["resources", "resources/locales/*"],
"resources": [
"resources",
"resources/locales/*"
],
"publisher": "Clash Verge Rev",
"externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
"externalBin": [
"sidecar/verge-mihomo",
"sidecar/verge-mihomo-alpha"
],
"copyright": "GNU General Public License v3.0",
"category": "DeveloperTool",
"shortDescription": "Clash Verge Rev",
@ -25,14 +32,17 @@
"devUrl": "http://localhost:3000/"
},
"productName": "Clash Verge",
"version": "2.1.2",
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
"endpoints": [
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json"
"https://gh-proxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json",
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha-proxy.json",
"https://gh-proxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha.json"
],
"windows": {
"installMode": "basicUi"
@ -40,15 +50,25 @@
},
"deep-link": {
"desktop": {
"schemes": ["clash", "clash-verge"]
"schemes": [
"clash",
"clash-verge"
]
}
}
},
"app": {
"security": {
"capabilities": ["desktop-capability", "migrated"],
"capabilities": [
"desktop-capability",
"migrated"
],
"assetProtocol": {
"scope": ["$APPDATA/**", "$RESOURCE/../**", "**"],
"scope": [
"$APPDATA/**",
"$RESOURCE/../**",
"**"
],
"enable": true
},
"csp": null

View File

@ -30,10 +30,5 @@
"./sidecar/verge-mihomo",
"./sidecar/verge-mihomo-alpha"
]
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico"
}
}
}

View File

@ -1,6 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"productName": "Clash Verge",
"bundle": {
"targets": ["app", "dmg"],
"macOS": {
@ -29,11 +30,5 @@
}
}
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon-mono.ico",
"iconAsTemplate": true
}
}
}

View File

@ -21,11 +21,6 @@
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true,
"showMenuOnLeftClick": false
},
"windows": []
}
}

View File

@ -30,11 +30,5 @@
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
}
}
}

View File

@ -30,11 +30,5 @@
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
}
}
}

View File

@ -30,11 +30,5 @@
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
}
}
}

13
src/App.tsx Normal file
View File

@ -0,0 +1,13 @@
import { AppDataProvider } from "./providers/app-data-provider";
import React from "react";
import Layout from "./pages/_layout";
function App() {
return (
<AppDataProvider>
<Layout />
</AppDataProvider>
);
}
export default App;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_3004_316)"/>
<path d="M23 14.6666H22.1667V13C22.1667 10.7 20.3 8.83331 18 8.83331C15.7 8.83331 13.8334 10.7 13.8334 13H15.5C15.5 11.6166 16.6167 10.5 18 10.5C19.3834 10.5 20.5 11.6166 20.5 13V14.6666H13C12.0834 14.6666 11.3334 15.4166 11.3334 16.3333V24.6666C11.3334 25.5833 12.0834 26.3333 13 26.3333H23C23.9167 26.3333 24.6667 25.5833 24.6667 24.6666V16.3333C24.6667 15.4166 23.9167 14.6666 23 14.6666ZM23 24.6666H13V16.3333H23V24.6666ZM18 22.1666C18.9167 22.1666 19.6667 21.4166 19.6667 20.5C19.6667 19.5833 18.9167 18.8333 18 18.8333C17.0834 18.8333 16.3334 19.5833 16.3334 20.5C16.3334 21.4166 17.0834 22.1666 18 22.1666Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_3004_316" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA800"/>
<stop offset="1" stop-color="#FFAC4B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1020 B

View File

@ -103,7 +103,6 @@ interface NoticeInstance {
let parent: HTMLDivElement = null!;
// @ts-ignore
export const Notice: NoticeInstance = (props) => {
const { type, message, duration } = props;
@ -142,8 +141,8 @@ export const Notice: NoticeInstance = (props) => {
);
};
(["info", "error", "success"] as const).forEach((type) => {
Notice[type] = (message: ReactNode, duration?: number) => {
const createNoticeTypeFactory =
(type: keyof NoticeInstance) => (message: ReactNode, duration?: number) => {
// 确保消息不为空
if (!message) {
return;
@ -156,4 +155,7 @@ export const Notice: NoticeInstance = (props) => {
duration: type === "error" ? 8000 : duration || 1500,
});
};
});
Notice.info = createNoticeTypeFactory("info");
Notice.error = createNoticeTypeFactory("error");
Notice.success = createNoticeTypeFactory("success");

21
src/components/center.tsx Normal file
View File

@ -0,0 +1,21 @@
import { Box, BoxProps } from "@mui/material";
import React from "react";
interface CenterProps extends BoxProps {
children: React.ReactNode;
}
export const Center: React.FC<CenterProps> = ({ children, ...props }) => {
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
width="100%"
height="100%"
{...props}
>
{children}
</Box>
);
};

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useLockFn } from "ahooks";
import { Box, Button, Snackbar } from "@mui/material";
import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { deleteConnection } from "@/services/api";
import parseTraffic from "@/utils/parse-traffic";
import { t } from "i18next";
@ -14,6 +14,7 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
(props, ref) => {
const [open, setOpen] = useState(false);
const [detail, setDetail] = useState<IConnectionsItem>(null!);
const theme = useTheme();
useImperativeHandle(ref, () => ({
open: (detail: IConnectionsItem) => {
@ -30,7 +31,15 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
open={open}
onClose={onClose}
sx={{ maxWidth: "520px" }}
sx={{
".MuiSnackbarContent-root": {
maxWidth: "520px",
maxHeight: "480px",
overflowY: "auto",
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
},
}}
message={
detail ? (
<InnerConnectionDetail data={detail} onClose={onClose} />
@ -48,6 +57,7 @@ interface InnerProps {
const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const { metadata, rulePayload } = data;
const theme = useTheme();
const chains = [...data.chains].reverse().join(" / ");
const rule = rulePayload ? `${data.rule}(${rulePayload})` : data.rule;
const host = metadata.host
@ -69,7 +79,10 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
label: t("UL Speed"),
value: parseTraffic(data.curUpload ?? -1).join(" ") + "/s",
},
{ label: t("Chains"), value: chains },
{
label: t("Chains"),
value: chains,
},
{ label: t("Rule"), value: rule },
{
label: t("Process"),
@ -90,10 +103,11 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const onDelete = useLockFn(async () => deleteConnection(data.id));
return (
<Box sx={{ userSelect: "text" }}>
<Box sx={{ userSelect: "text", color: theme.palette.text.secondary }}>
{information.map((each) => (
<div key={each.label}>
<b>{each.label}</b>: <span>{each.value}</span>
<b>{each.label}</b>
<span style={{ wordBreak: "break-all", color: theme.palette.text.primary }}>: {each.value}</span>
</div>
))}

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