Compare commits

...
264 Commits
Author SHA1 Message Date
MystiPanda 2f77a07b3d Release 1.7.4 2024-07-15 20:56:18 +08:00
MystiPanda 4cd7ccda57 fix: unified switch style 2024-07-15 20:42:00 +08:00
MystiPanda ef337ffb69 feat: optimize the service mode interaction logic. 2024-07-15 20:02:05 +08:00
MystiPanda 9ff5d8d4e0 Revert "fix: try to fix abnormal stuck"
This reverts commit e14ddbe23c.
2024-07-14 12:49:06 +08:00
Sukka 199a5eed60 fix(#1387): avoid catastrophic backtracking (#1396) 2024-07-14 12:26:02 +08:00
MystiPanda e14ddbe23c fix: try to fix abnormal stuck
#1387
2024-07-13 21:41:04 +08:00
MystiPanda 3878e66ea4 chore: update locale 2024-07-13 19:15:07 +08:00
MystiPanda c14a33444b chore: unified icon style 2024-07-13 19:01:16 +08:00
MystiPanda 48f7c15035 feat: display network interface 2024-07-13 14:15:13 +08:00
Avan 32212a46e2 feature: copy clash env (#1391)
* feat: copy clash env

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

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

* chore: use import path alias

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

* refactor: use swr subscription for memory & traffic

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

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

* clean up
2024-06-07 15:51:51 +08:00
Sukka 66dd510acc refactor: replace recoil (#1137) 2024-06-07 12:27:37 +08:00
wonfen 96e044566c Style: modify proxy pages 2024-06-07 10:32:27 +08:00
MystiPanda 726ad84d7f fix: sign error 2024-06-06 16:06:58 +08:00
wonfen 4fc422f880 chore: apple developer sign test 2024-06-06 15:21:53 +08:00
dongchengjie 1938024ab3 chore: test_delay trace 2024-06-05 19:17:43 +08:00
dongchengjie 05b1de6fd5 chore: theme colors to uppercase 2024-06-05 14:20:24 +08:00
dongchengjie 0bf8807b50 build: no legacy chunks 2024-06-05 09:04:08 +08:00
Sukka 23668ee74e fix(#1126): add Object.hasOwn polyfill (#1127) 2024-06-05 00:12:06 +08:00
dongchengjie 6bc5c5a808 fix: csp missing asset: 2024-06-04 15:26:39 +08:00
MystiPanda 330004dc72 build: update depends 2024-06-04 12:16:42 +08:00
MystiPanda a900d6d742 Release 1.6.5 2024-06-04 12:11:38 +08:00
MystiPanda bfea70eec9 fix: package error 2024-06-04 10:47:17 +08:00
MystiPanda 13babbf330 feat: support rpm package 2024-06-04 10:16:25 +08:00
MystiPanda 0a6fe382ac build: use latest tauri 2024-06-04 10:09:04 +08:00
Zhenfu Shi dd6dea9dfc chore: Enable Ad Hoc signing on macOS (#1117)
Closes #1116
2024-06-03 09:01:33 +08:00
wonfen 11434fba68 Revert "chore: change default test url"
This reverts commit 69b65d06a4.
2024-06-03 08:25:06 +08:00
dongchengjie 10bf90c92a chore: use icons files instead of hard coding 2024-06-03 07:21:40 +08:00
dongchengjie 5193fe4bab fix: group selected be overwritten when saving test 2024-06-03 06:38:20 +08:00
dongchengjie 4df4995623 chore: improve UI in connections page 2024-06-02 21:52:31 +08:00
aixiao0621 f5f4ecf46a fix: a display error on the connections page 2024-06-02 17:26:23 +08:00
MystiPanda 69b65d06a4 chore: change default test url 2024-06-01 23:14:47 +08:00
MystiPanda e6bf4e836d build: update depends 2024-06-01 23:01:27 +08:00
dongchengjie 01d67eb239 refactor: polyfills review 2024-05-30 20:27:12 +08:00
dongchengjie 3514cfbd44 fix: research when search box mode changes 2024-05-30 10:45:24 +08:00
dongchengjie cade35fe10 chore: Content-Security-Policy 2024-05-29 10:20:56 +08:00
dongchengjie 2c6df77f5a ci: fix heap oom 2024-05-29 09:49:11 +08:00
dongchengjie 0276ae91b5 build: polyfills 2024-05-29 09:39:26 +08:00
dongchengjie 60fee2accb build: import babel 2024-05-28 09:59:41 +08:00
dongchengjie 03e2632294 fix: try to fix #1084 2024-05-27 23:19:44 +08:00
dongchengjie 194fe59458 typo: inconsistent style in layout-viewer 2024-05-27 16:37:48 +08:00
MystiPanda e400111f8a Release 1.6.4 2024-05-26 22:07:53 +08:00
MystiPanda 8bc7607783 Revert "refactor: use axios tauri adapter"
This reverts commit 7e361cb3f1.
2024-05-26 21:18:02 +08:00
dongchengjie 4108451ebe chore: %mixed-port% hint for PAC script 2024-05-26 19:53:42 +08:00
MystiPanda 0f56257aa1 Release 1.6.3 2024-05-26 19:43:26 +08:00
MystiPanda 2a575284b9 chore: add content type 2024-05-26 19:26:57 +08:00
MystiPanda 444bcbfb30 fix: auto proxy changed by guard 2024-05-26 19:07:14 +08:00
MystiPanda 212021c878 feat: Support PAC Mode 2024-05-26 17:59:39 +08:00
dongchengjie 600b687253 chore: emoji display support in editor 2024-05-25 18:02:32 +08:00
farzadhallaji ae3aada172 Update fa.json (#1060) 2024-05-24 19:39:40 +08:00
MystiPanda 50cc8af8c3 ci: use rust 1.77.0 2024-05-23 11:47:41 +08:00
dongchengjie 1809cdbf37 fix: no snippets and warnings in runtime config editor 2024-05-21 06:18:09 +08:00
dongchengjie fb38047769 chore: update locale in connection 2024-05-20 13:42:45 +08:00
dongchengjie 08e0d6a34a chore: text overflow word-wrap & cleanup 2024-05-20 12:38:41 +08:00
MystiPanda b4fddaec7f build: use compatible 2024-05-19 19:30:53 +08:00
MystiPanda 3f19d22941 fix: installer package error 2024-05-19 19:13:21 +08:00
MystiPanda c7d51cfc4c build: use latest core 2024-05-19 13:00:27 +08:00
dongchengjie 6829d34e90 chore: schema 1.18.5-alpha 2024-05-19 12:37:18 +08:00
dongchengjie 021e430103 chore: fix select component bg in connection 2024-05-18 18:42:51 +08:00
dongchengjie ae6f42a7fb chore: fix search-box bg 2024-05-18 18:20:05 +08:00
MystiPanda da98097394 chore: support more asset scope 2024-05-18 15:47:45 +08:00
oomeow 43f0b935cf perf: memoize the proxy col items (#1029) 2024-05-18 15:14:22 +08:00
oomeow db5d14e0ce fix: clipboard doesn't work and set_shadow method is not supported in Linux (#1030) 2024-05-18 15:14:00 +08:00
dongchengjie b25cf5eadb feat: editor import PAC definition 2024-05-18 14:59:17 +08:00
dongchengjie bd3b41c809 chore: hint for canceling fixed #840 2024-05-17 20:44:18 +08:00
dongchengjie 31d301064a chore: use search-box in logs and connections 2024-05-17 19:44:42 +08:00
dongchengjie b95b9bdaf3 chore: component base-search-box 2024-05-17 19:13:33 +08:00
dongchengjie 937f0b5687 chore: fix editor dialog scroll overflow 2024-05-17 04:03:49 +08:00
dongchengjie ffbc892e44 fix: missing locale 2024-05-15 23:27:31 +08:00
dongchengjie 9e872932d1 chore: current bypass wrap 2024-05-15 22:59:53 +08:00
MystiPanda ecb2fbf900 feat: support monochrome tray icon 2024-05-15 14:50:10 +08:00
dongchengjie 72edd2e15d chore: logo svg 2024-05-14 16:53:39 +08:00
dongchengjie c3d22c554f feat: doc reference link on settings header 2024-05-14 14:40:47 +08:00
dongchengjie e72f4a1582 chore: use coding fonts in editor 2024-05-14 14:20:50 +08:00
dongchengjie 5e43c060fa fix: switch missing break 2024-05-14 01:24:23 +08:00
dongchengjie e8dee1ddd9 chore: @ts-ignore schema check 2024-05-14 01:21:28 +08:00
dongchengjie d85b8c776f chore: revert schema to beta4 2024-05-14 00:59:05 +08:00
dongchengjie fb518b6218 chore: palette locale 2024-05-13 23:12:29 +08:00
dongchengjie 8d510cde21 typo: unused import 2024-05-13 22:59:24 +08:00
dongchengjie da4bf167ee feat: css injection editor 2024-05-13 22:58:25 +08:00
Remember dc1abf8dcb Update 172.16.0.0/12 on Windows (#1013) 2024-05-13 18:28:41 +08:00
MystiPanda 3fc969a50b fix: use default bypass when empty 2024-05-13 18:27:19 +08:00
MystiPanda 4064129d49 chore: Adapt Mac icon 2024-05-13 13:11:05 +08:00
MystiPanda 22b4e0bfff docs: Update README 2024-05-12 22:17:05 +08:00
MystiPanda 53fa1f88fd chore: Change HomePage Logo 2024-05-12 21:17:21 +08:00
MystiPanda 5fefcd92f3 chore: Try a new icon 2024-05-12 19:31:06 +08:00
dongchengjie d20bc936da chore: ru locale 2024-05-12 16:18:49 +08:00
RikudouPatrickstar e79f036a70 chore: update notification message and zh translation (#1011) 2024-05-12 14:40:18 +08:00
MystiPanda 0f2af91a04 perf: Disable Tun mode before shutting down 2024-05-12 11:06:44 +08:00
MystiPanda 7e361cb3f1 refactor: use axios tauri adapter 2024-05-11 21:54:56 +08:00
MystiPanda 1f7acd4027 feat: Allow disable unused ports 2024-05-09 09:22:32 +08:00
MystiPanda dddca7bf4b chore: Update locale 2024-05-08 23:45:55 +08:00
MystiPanda e520be80c0 perf: Limit drawing frame rate 2024-05-08 22:33:13 +08:00
MystiPanda 0b78dedf04 fix: bundle error 2024-05-08 14:52:38 +08:00
MystiPanda 8f409477c8 build: fix bundle error 2024-05-08 14:20:30 +08:00
MystiPanda 9c22f7ce69 fix: depend error 2024-05-08 13:24:34 +08:00
MystiPanda 05fa916915 Release 1.6.2 2024-05-08 13:14:00 +08:00
MystiPanda 9a593d7a61 feat: support upgrade for release core 2024-05-08 12:48:27 +08:00
MystiPanda 30017eeaa0 docs: Update FAQ URL 2024-05-08 11:44:57 +08:00
MystiPanda 9b03cacc94 ci: update ci 2024-05-08 11:12:06 +08:00
MystiPanda 557970cf54 refactor: remove prepend and append for provider 2024-05-08 00:22:14 +08:00
MystiPanda d90dac60ef feat: deep merge
#983
2024-05-08 00:13:32 +08:00
MystiPanda b4b2e67260 build: Add portable for fixed webview2 2024-05-06 19:13:08 +08:00
MystiPanda bcd5e935e7 ci: debug ci 2024-05-06 16:16:47 +08:00
MystiPanda b322627609 ci: fix ci script 2024-05-06 15:30:00 +08:00
MystiPanda 09861095ff ci: update release body 2024-05-06 15:15:07 +08:00
MystiPanda 47e567fb40 docs: update url 2024-05-06 15:05:56 +08:00
MystiPanda 5bb30ad28f build: try support fixed webview2 runtime 2024-05-06 14:18:12 +08:00
MystiPanda a46bbf05ec ci: try resupport x86 2024-05-06 13:04:29 +08:00
dongchengjie 04a4705dbd fix: non-ascii character secret causes controller link error
https://github.com/clash-verge-rev/clash-verge-rev/issues/973#issuecomment-2094839700
2024-05-05 23:04:33 +08:00
201 changed files with 14436 additions and 4684 deletions
+2 -2
View File
@@ -8,13 +8,13 @@ body:
attributes: attributes:
value: | value: |
## 在提交问题之前,请确认以下事项: ## 在提交问题之前,请确认以下事项:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/install/) 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下进行讨论 2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索 3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在 4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭 5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
## Before submitting the issue, please make sure of the following checklist: ## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) and [FAQ](https://clash-verge-rev.github.io/faq/install/) 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 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 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 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
+2 -2
View File
@@ -8,13 +8,13 @@ body:
attributes: attributes:
value: | value: |
## 在提交问题之前,请确认以下事项: ## 在提交问题之前,请确认以下事项:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 确认软件不存在类似的功能 1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 确认软件不存在类似的功能
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论 2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索 3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现 4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭 5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
## Before submitting the issue, please make sure of the following checklist: ## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) to confirm that the software does not have similar functions 1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) to confirm that the software does not have similar functions
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue 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 3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented 4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
+2 -5
View File
@@ -1,8 +1,5 @@
pnpm install pnpm install
pnpm check $INPUT_TARGET pnpm check $INPUT_TARGET
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
pnpm build --target $INPUT_TARGET pnpm build --target $INPUT_TARGET
else
pnpm build --target $INPUT_TARGET -b deb
fi
+6
View File
@@ -10,6 +10,12 @@ rustup target add "$INPUT_TARGET"
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
apt-get update apt-get update
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
elif [ "$INPUT_TARGET" = "i686-unknown-linux-gnu" ]; then
dpkg --add-architecture i386
apt-get update
apt-get install -y libstdc++6:i386 libgdk-pixbuf2.0-dev:i386 libatomic1:i386 gcc-multilib g++-multilib libwebkit2gtk-4.0-dev:i386 libssl-dev:i386 libgtk-3-dev:i386 librsvg2-dev:i386 patchelf:i386 libayatana-appindicator3-dev:i386
export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
dpkg --add-architecture arm64 dpkg --add-architecture arm64
apt-get update apt-get update
+128 -45
View File
@@ -9,6 +9,10 @@ permissions: write-all
env: env:
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short 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: jobs:
alpha: alpha:
@@ -18,6 +22,8 @@ jobs:
include: include:
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
- os: windows-latest
target: i686-pc-windows-msvc
- os: windows-latest - os: windows-latest
target: aarch64-pc-windows-msvc target: aarch64-pc-windows-msvc
- os: macos-latest - os: macos-latest
@@ -31,7 +37,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Rust Stable - name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@1.77.0
- name: Add Rust Target - name: Add Rust Target
run: rustup target add ${{ matrix.target }} run: rustup target add ${{ matrix.target }}
@@ -40,16 +46,17 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: src-tauri workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node - name: Install Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "20"
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
name: Install pnpm name: Install pnpm
with: with:
version: 9
run_install: false run_install: false
- name: Pnpm install and check - name: Pnpm install and check
@@ -60,9 +67,16 @@ jobs:
- name: Tauri build - name: Tauri build
uses: tauri-apps/tauri-action@v0 uses: tauri-apps/tauri-action@v0
env: env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with: with:
tagName: alpha tagName: alpha
releaseName: "Clash Verge Rev Alpha" releaseName: "Clash Verge Rev Alpha"
@@ -77,8 +91,6 @@ jobs:
run: pnpm portable ${{ matrix.target }} --alpha run: pnpm portable ${{ matrix.target }} --alpha
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
alpha-for-linux: alpha-for-linux:
strategy: strategy:
@@ -87,6 +99,8 @@ jobs:
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: i686-unknown-linux-gnu
- os: ubuntu-latest - os: ubuntu-latest
target: aarch64-unknown-linux-gnu target: aarch64-unknown-linux-gnu
- os: ubuntu-latest - os: ubuntu-latest
@@ -99,6 +113,7 @@ jobs:
- name: Build for Linux - name: Build for Linux
uses: ./.github/build-for-linux uses: ./.github/build-for-linux
env: env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with: with:
@@ -111,52 +126,106 @@ jobs:
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- run: |
cat > release.txt << 'EOF'
### 我应该下载哪个版本?
- Windows x86_64架构: x64-setup.exe (不支持win7)
- Windows arm64架构: arm64-setup.exe
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
- Linux x64架构: amd64.AppImage/amd64.deb
- Linux arm64架构: arm64.deb
- Linux armv7架构: armhf.deb
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
### FAQ
- [FAQ](https://clash-verge-rev.github.io/faq/install/)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release - name: Upload Release
if: startsWith(matrix.target, 'x86_64') uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
with: with:
tag_name: alpha tag_name: alpha
name: "Clash Verge Rev Alpha" name: "Clash Verge Rev Alpha"
body_path: release.txt body: "More new features are now supported."
prerelease: true prerelease: true
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage* files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-for-fixed-webview2:
strategy:
fail-fast: false
matrix:
include:
- 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
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Rename
run: |
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.exe' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.exe'
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip'
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip.sig' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip.sig'
- name: Upload Release - name: Upload Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
tag_name: alpha tag_name: alpha
name: "Clash Verge Rev Alpha" name: "Clash Verge Rev Alpha"
body_path: release.txt body: "More new features are now supported."
prerelease: true prerelease: true
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_tag: update_tag:
name: Update tag name: Update tag
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [alpha, alpha-for-linux] needs: [alpha, alpha-for-linux, alpha-for-fixed-webview2]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -175,27 +244,41 @@ jobs:
- run: | - run: |
cat > release.txt << 'EOF' cat > release.txt << 'EOF'
### 我应该下载哪个版本? ## 我应该下载哪个版本?
- Windows x86_64架构: x64-setup.exe (不支持win7) ### MacOS (提示文件损坏或开发者无法验证请查看下面FAQ)
- Windows arm64架构: arm64-setup.exe
- MacOS intel芯片: x64.dmg - MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ) - MacOS apple M芯片: aarch64.dmg
- Linux x64架构: amd64.AppImage/amd64.deb
- Linux arm64架构: arm64.deb ### Linux
- Linux armv7架构: armhf.deb - Linux 64位: amd64.deb/amd64.rpm
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新) - Linux 32位: i386.deb/i386.rpm
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新) - Linux arm64架构: arm64.deb/aarch64.rpm
- Linux armv7架构: armhf.deb/armhfp.rpm
### Windows (Win7 用户请查看下面FAQ中的解决方案)
#### 正常版本(推荐)
- 64位: x64-setup.exe
- 32位: x86-setup.exe
- arm64架构: arm64-setup.exe
#### 便携版(不推荐使用,无法自动更新)
- 64位: x64_portable.zip
- 32位: x86_portable.zip
- arm64架构: arm64_portable.zip
#### 内置Webview2版(体积较大,仅在企业版系统或Win7无法安装webview2时使用)
- 64位: x64_fixed_webview2-setup.exe
- 32位: x86_fixed_webview2-setup.exe
- arm64架构: arm64_fixed_webview2-setup.exe
### FAQ ### FAQ
- [FAQ](https://clash-verge-rev.github.io/faq/install/) - [FAQ](https://clash-verge-rev.github.io/faq/windows.html)
Created at ${{ env.BUILDTIME }}. Created at ${{ env.BUILDTIME }}.
EOF EOF
- name: Upload Release - name: Upload Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
tag_name: alpha tag_name: alpha
name: "Clash Verge Rev Alpha" name: "Clash Verge Rev Alpha"
+86
View File
@@ -0,0 +1,86 @@
name: Development Test
on:
workflow_dispatch:
permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
concurrency:
# only allow per workflow per commit (and not pr) to run at a time
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
dev:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
bundle: nsis
- os: macos-latest
target: aarch64-apple-darwin
bundle: dmg
- os: macos-latest
target: x86_64-apple-darwin
bundle: dmg
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@1.77.0
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }} -b ${{ matrix.bundle }}
- name: Upload Artifacts
if: matrix.os == 'macos-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
if-no-files-found: error
- name: Upload Artifacts
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe
if-no-files-found: error
+181 -65
View File
@@ -15,6 +15,8 @@ jobs:
include: include:
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
- os: windows-latest
target: i686-pc-windows-msvc
- os: windows-latest - os: windows-latest
target: aarch64-pc-windows-msvc target: aarch64-pc-windows-msvc
- os: macos-latest - os: macos-latest
@@ -28,7 +30,120 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Rust Stable - name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@1.77.0
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: v__VERSION__
releaseName: "Clash Verge Rev v__VERSION__"
releaseBody: "More new features are now supported."
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:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: i686-unknown-linux-gnu
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Build for Linux
uses: ./.github/build-for-linux
env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
target: ${{ matrix.target }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{env.VERSION}}
name: "Clash Verge Rev v${{env.VERSION}}"
body: "More new features are now supported."
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
release-for-fixed-webview2:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: i686-pc-windows-msvc
arch: x86
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Add Rust Target - name: Add Rust Target
run: rustup target add ${{ matrix.target }} run: rustup target add ${{ matrix.target }}
@@ -43,10 +158,9 @@ jobs:
with: with:
node-version: "20" node-version: "20"
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v4
name: Install pnpm name: Install pnpm
with: with:
version: 9
run_install: false run_install: false
- name: Pnpm install and check - name: Pnpm install and check
@@ -54,94 +168,60 @@ jobs:
pnpm i pnpm i
pnpm check ${{ matrix.target }} pnpm check ${{ matrix.target }}
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build - name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0 uses: tauri-apps/tauri-action@v0
env: env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with: with:
tagName: v__VERSION__
releaseName: "Clash Verge Rev v__VERSION__"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: false
tauriScript: pnpm tauriScript: pnpm
args: --target ${{ matrix.target }} args: --target ${{ matrix.target }}
- name: Rename
run: |
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.exe' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.exe'
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip'
Rename-Item '.\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}-setup.nsis.zip.sig' 'Clash Verge_${{steps.build.outputs.appVersion}}_${{ matrix.arch }}_fixed_webview2-setup.nsis.zip.sig'
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{steps.build.outputs.appVersion}}
name: "Clash Verge Rev v${{steps.build.outputs.appVersion}}"
body: "More new features are now supported."
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle - name: Portable Bundle
if: matrix.os == 'windows-latest' run: pnpm portable-fixed-webview2 ${{ matrix.target }}
run: pnpm portable ${{ matrix.target }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
release-for-linux:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Build for Linux
uses: ./.github/build-for-linux
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
target: ${{ matrix.target }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
- name: Upload Release
if: startsWith(matrix.target, 'x86_64')
uses: softprops/action-gh-release@v1
with:
tag_name: v${{env.VERSION}}
name: "Clash Verge Rev v${{env.VERSION}}"
body: "More new features are now supported."
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
- name: Upload Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{env.VERSION}}
name: "Clash Verge Rev v${{env.VERSION}}"
body: "More new features are now supported."
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
release-update: release-update:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [release, release-for-linux] needs: [release, release-for-linux]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install Node - name: Install Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "20"
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v4
name: Install pnpm name: Install pnpm
with: with:
version: 8
run_install: false run_install: false
- name: Pnpm install - name: Pnpm install
@@ -151,3 +231,39 @@ jobs:
run: pnpm updater run: pnpm updater
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update-for-fixed-webview2:
runs-on: ubuntu-latest
needs: [release-for-fixed-webview2]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install
run: pnpm i
- name: Release updater file
run: pnpm updater-fixed-webview2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
submit-to-winget:
runs-on: windows-latest
needs: [release-update]
steps:
- name: Submit to Winget
uses: vedantmgoyal9/winget-releaser@main
with:
identifier: ClashVergeRev.ClashVergeRev
installers-regex: '_(arm64|x64|x86)-setup\.exe$'
token: ${{ secrets.GITHUB_TOKEN }}
+27 -4
View File
@@ -7,17 +7,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Install Node - name: Install Node
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "20"
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v4
name: Install pnpm name: Install pnpm
with: with:
version: 8
run_install: false run_install: false
- name: Pnpm install - name: Pnpm install
@@ -27,3 +26,27 @@ jobs:
run: pnpm updater run: pnpm updater
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-update-for-fixed-webview2:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install
run: pnpm i
- name: Release updater file
run: pnpm updater-fixed-webview2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -1
View File
@@ -7,4 +7,5 @@ dist-ssr
update.json update.json
scripts/_env.sh scripts/_env.sh
.vscode .vscode
.tool-versions .tool-versions
.idea
+5 -3
View File
@@ -1,5 +1,5 @@
<h1 align="center"> <h1 align="center">
<img src="./src/assets/image/logo.png" alt="Clash" width="128" /> <img src="./src-tauri/icons/icon.png" alt="Clash" width="128" />
<br> <br>
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a> Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
<br> <br>
@@ -11,7 +11,9 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
## Preview ## Preview
![preview](./docs/preview.png) | Dark | Light |
| -------------------------------- | --------------------------------- |
| ![预览](./docs/preview_dark.png) | ![预览](./docs/preview_light.png) |
## Install ## Install
@@ -49,7 +51,7 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
### FAQ ### FAQ
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/install/) Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/windows.html)
## Development ## Development
+228
View File
@@ -1,3 +1,231 @@
## v1.7.4
### Features
- 展示局域网 IP 地址信息
- 在设置页面直接复制环境变量
- 优化服务模式安装逻辑
### Performance
- 优化切换订阅速度
- 优化更改端口速度
### Bugs Fixes
- 调整 MacOS 托盘图标大小
- Trojan URI 解析错误
- 卡片拖动显示层级错误
- 代理绕过格式检查错误
- MacOS 下编辑器最大化失败
- MacOS 服务安装失败
- 更改窗口大小导致闪退的问题
---
## v1.7.3
### Features
- 支持可视化编辑订阅代理组
- 支持可视化编辑订阅节点
- 支持可视化编辑订阅规则
- 扩展脚本支持订阅名称参数 `function main(config, profileName)`
### Bugs Fixes
- 代理绕过格式检查错误
---
## v1.7.2
### Break Changes
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
- 订阅扩展配置在订阅右键菜单里进入
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
- MacOS 用户更新后请重新安装服务模式
### Features
- 升级内核到 1.18.6
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
- 各种细节优化和界面性能优化
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.7.1
### Break Changes
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
- 订阅扩展配置在订阅右键菜单里进入
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
- MacOS 用户更新后请重新安装服务模式
### Features
- 升级内核到 1.18.6
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
- 各种细节优化和界面性能优化
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.7.0
### Break Changes
- 此版本重构了 Merge/Script,更新前请先备份好自定义 Merge 和 Script(更新并不会删除配置文件,但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置` 和 `订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本` 和 `订阅扩展脚本`
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- MacOS 用户更新后请重新安装服务模式
### Features
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex,并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.6.6
### Features
- MacOS 应用签名
- 删除 AppImage
- 应用更新对话框添加下载按钮
- 设置系统代理绕过时保留默认值
- 系统代理绕过设置输入格式检查
### Bugs Fixes
- MacOS 代理组图标无法显示
- RPM 包依赖缺失
---
## v1.6.5
### Features
- 添加 RPM 包支持
- 优化细节
### Bugs Fixes
- MacOS 10.15 编辑器空白的问题
- MacOS 低版本启动白屏的问题
---
## v1.6.4
### Features
- 系统代理支持 PAC 模式
- 允许关闭不使用的端口
- 使用新的应用图标
- MacOS 支持切换托盘图标单色/彩色模式
- CSS 注入支持通过编辑器编辑
- 优化代理组列表性能
- 优化流量图显性能
- 支持波斯语
### Bugs Fixes
- Kill 内核后 Tun 开启缓慢的问题
- 代理绕过为空时使用默认值
- 无法读取剪切板内容
- Windows 下覆盖安装无法内核占用问题
---
## v1.6.2
### Features
- 支持本地文件拖拽导入
- 重新支持 32 位 CPU
- 新增内置 Webview2 版本
- 优化 Merge 逻辑,支持深度合并
- 删除 Merge 配置中的 append/prepend-provider 字段
- 支持更新稳定版内核
### Bugs Fixes
- MacOS DNS 还原失败
- CMD 环境变量格式错误
- Linux 下与 N 卡的兼容性问题
- 修改 Tun 设置不立即生效
---
## v1.6.1 ## v1.6.1
### Features ### Features
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

+37 -25
View File
@@ -1,6 +1,6 @@
{ {
"name": "clash-verge", "name": "clash-verge",
"version": "1.6.1", "version": "1.7.4",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"dev": "tauri dev", "dev": "tauri dev",
@@ -12,7 +12,9 @@
"web:serve": "vite preview", "web:serve": "vite preview",
"check": "node scripts/check.mjs", "check": "node scripts/check.mjs",
"updater": "node scripts/updater.mjs", "updater": "node scripts/updater.mjs",
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
"portable": "node scripts/portable.mjs", "portable": "node scripts/portable.mjs",
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
"prepare": "husky install" "prepare": "husky install"
}, },
"dependencies": { "dependencies": {
@@ -22,45 +24,53 @@
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@juggle/resize-observer": "^3.4.0", "@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^5.15.15", "@mui/icons-material": "^5.16.0",
"@mui/lab": "5.0.0-alpha.149", "@mui/lab": "5.0.0-alpha.149",
"@mui/material": "^5.15.15", "@mui/material": "^5.16.0",
"@mui/x-data-grid": "^6.19.11", "@mui/x-data-grid": "^7.9.0",
"@tauri-apps/api": "^1.5.4", "@tauri-apps/api": "^1.6.0",
"@types/json-schema": "^7.0.15", "@types/json-schema": "^7.0.15",
"ahooks": "^3.7.11", "ahooks": "^3.8.0",
"axios": "^1.6.8", "axios": "^1.7.2",
"dayjs": "1.11.5", "dayjs": "1.11.5",
"i18next": "^23.11.2", "foxact": "^0.2.35",
"i18next": "^23.11.5",
"js-base64": "^3.7.7",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"meta-json-schema": "1.18.4-beta2", "meta-json-schema": "1.18.6",
"monaco-editor": "^0.47.0", "monaco-editor": "^0.49.0",
"monaco-yaml": "^5.1.1", "monaco-yaml": "^5.2.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"peggy": "^4.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-hook-form": "^7.51.3", "react-hook-form": "^7.52.1",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-router-dom": "^6.23.0", "react-monaco-editor": "^0.55.0",
"react-router-dom": "^6.24.1",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-virtuoso": "^4.7.10", "react-virtuoso": "^4.7.11",
"recoil": "^0.7.7", "sockette": "^2.0.6",
"swr": "^1.3.0", "swr": "^2.2.5",
"tar": "^6.2.1" "tar": "^6.2.1",
"types-pac": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@actions/github": "^5.1.1", "@actions/github": "^5.1.1",
"@tauri-apps/cli": "^1.5.12", "@tauri-apps/cli": "^1.6.0",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/react": "^18.3.1", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-transition-group": "^4.4.10", "@types/react-transition-group": "^4.4.10",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-legacy": "^5.4.1",
"adm-zip": "^0.5.12", "@vitejs/plugin-react": "^4.3.1",
"adm-zip": "^0.5.14",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"https-proxy-agent": "^5.0.1", "https-proxy-agent": "^5.0.1",
@@ -68,9 +78,10 @@
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"pretty-quick": "^3.3.1", "pretty-quick": "^3.3.1",
"sass": "^1.75.0", "sass": "^1.77.6",
"typescript": "^5.4.5", "terser": "^5.31.1",
"vite": "^5.2.10", "typescript": "^5.5.3",
"vite": "^5.3.3",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svgr": "^4.2.0" "vite-plugin-svgr": "^4.2.0"
}, },
@@ -79,5 +90,6 @@
"semi": true, "semi": true,
"singleQuote": false, "singleQuote": false,
"endOfLine": "lf" "endOfLine": "lf"
} },
"packageManager": "pnpm@9.1.4"
} }
+2699 -827
View File
File diff suppressed because it is too large Load Diff
+8 -20
View File
@@ -61,7 +61,7 @@ const META_ALPHA_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible", "win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386", "win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64", "win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64", "darwin-x64": "mihomo-darwin-amd64-compatible",
"darwin-arm64": "mihomo-darwin-arm64", "darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible", "linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386", "linux-ia32": "mihomo-linux-386",
@@ -108,7 +108,7 @@ const META_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible", "win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386", "win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64", "win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64", "darwin-x64": "mihomo-darwin-amd64-compatible",
"darwin-arm64": "mihomo-darwin-arm64", "darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible", "linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386", "linux-ia32": "mihomo-linux-386",
@@ -172,8 +172,8 @@ function clashMetaAlpha() {
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`; const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
return { return {
name: "clash-meta-alpha", name: "verge-mihomo-alpha",
targetFile: `clash-meta-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`, targetFile: `verge-mihomo-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
exeFile, exeFile,
zipFile, zipFile,
downloadURL, downloadURL,
@@ -189,8 +189,8 @@ function clashMeta() {
const zipFile = `${name}-${META_VERSION}.${urlExt}`; const zipFile = `${name}-${META_VERSION}.${urlExt}`;
return { return {
name: "clash-meta", name: "verge-mihomo",
targetFile: `clash-meta-${SIDECAR_HOST}${isWin ? ".exe" : ""}`, targetFile: `verge-mihomo-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
exeFile, exeFile,
zipFile, zipFile,
downloadURL, downloadURL,
@@ -400,16 +400,6 @@ const resolveUninstall = () => {
}); });
}; };
const resolveSetDnsScript = () =>
resolveResource({
file: "set_dns.sh",
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/set_dns.sh`,
});
const resolveUnSetDnsScript = () =>
resolveResource({
file: "unset_dns.sh",
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/unset_dns.sh`,
});
const resolveMmdb = () => const resolveMmdb = () =>
resolveResource({ resolveResource({
file: "Country.mmdb", file: "Country.mmdb",
@@ -434,13 +424,13 @@ const resolveEnableLoopback = () =>
const tasks = [ const tasks = [
// { name: "clash", func: resolveClash, retry: 5 }, // { name: "clash", func: resolveClash, retry: 5 },
{ {
name: "clash-meta-alpha", name: "verge-mihomo-alpha",
func: () => func: () =>
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())), getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
retry: 5, retry: 5,
}, },
{ {
name: "clash-meta", name: "verge-mihomo",
func: () => func: () =>
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())), getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
retry: 5, retry: 5,
@@ -449,8 +439,6 @@ const tasks = [
{ name: "service", func: resolveService, retry: 5 }, { name: "service", func: resolveService, retry: 5 },
{ name: "install", func: resolveInstall, retry: 5 }, { name: "install", func: resolveInstall, retry: 5 },
{ name: "uninstall", func: resolveUninstall, retry: 5 }, { name: "uninstall", func: resolveUninstall, retry: 5 },
{ name: "set_dns_script", func: resolveSetDnsScript, retry: 5 },
{ name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 },
{ name: "mmdb", func: resolveMmdb, retry: 5 }, { name: "mmdb", func: resolveMmdb, retry: 5 },
{ name: "geosite", func: resolveGeosite, retry: 5 }, { name: "geosite", func: resolveGeosite, retry: 5 },
{ name: "geoip", func: resolveGeoIP, retry: 5 }, { name: "geoip", func: resolveGeoIP, retry: 5 },
+100
View File
@@ -0,0 +1,100 @@
import fs from "fs-extra";
import path from "path";
import AdmZip from "adm-zip";
import { createRequire } from "module";
import { getOctokit, context } from "@actions/github";
const target = process.argv.slice(2)[0];
const alpha = process.argv.slice(2)[1];
const ARCH_MAP = {
"x86_64-pc-windows-msvc": "x64",
"i686-pc-windows-msvc": "x86",
"aarch64-pc-windows-msvc": "arm64",
};
const PROCESS_MAP = {
x64: "x64",
ia32: "x86",
arm64: "arm64",
};
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
/// Script for ci
/// 打包绿色版/便携版 (only Windows)
async function resolvePortable() {
if (process.platform !== "win32") return;
const releaseDir = target
? `./src-tauri/target/${target}/release`
: `./src-tauri/target/release`;
const configDir = path.join(releaseDir, ".config");
if (!(await fs.pathExists(releaseDir))) {
throw new Error("could not found the release dir");
}
await fs.mkdir(configDir);
await fs.createFile(path.join(configDir, "PORTABLE"));
const zip = new AdmZip();
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
zip.addLocalFolder(
path.join(
releaseDir,
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
),
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
);
zip.addLocalFolder(configDir, ".config");
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
const { version } = packageJson;
const zipFile = `Clash.Verge_${version}_${arch}_fixed_webview2_portable.zip`;
zip.writeZip(zipFile);
console.log("[INFO]: create portable zip successfully");
// push release assets
if (process.env.GITHUB_TOKEN === undefined) {
throw new Error("GITHUB_TOKEN is required");
}
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const tag = alpha ? "alpha" : process.env.TAG_NAME || `v${version}`;
console.log("[INFO]: upload to ", tag);
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag,
});
let assets = release.assets.filter((x) => {
return x.name === zipFile;
});
if (assets.length > 0) {
let id = assets[0].id;
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: id,
});
}
console.log(release.name);
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: release.id,
name: zipFile,
data: zip.toBuffer(),
});
}
resolvePortable().catch(console.error);
+10 -3
View File
@@ -9,9 +9,16 @@ const alpha = process.argv.slice(2)[1];
const ARCH_MAP = { const ARCH_MAP = {
"x86_64-pc-windows-msvc": "x64", "x86_64-pc-windows-msvc": "x64",
"i686-pc-windows-msvc": "x86",
"aarch64-pc-windows-msvc": "arm64", "aarch64-pc-windows-msvc": "arm64",
}; };
const PROCESS_MAP = {
x64: "x64",
ia32: "x86",
arm64: "arm64",
};
const arch = target ? ARCH_MAP[target] : PROCESS_MAP[process.arch];
/// Script for ci /// Script for ci
/// 打包绿色版/便携版 (only Windows) /// 打包绿色版/便携版 (only Windows)
async function resolvePortable() { async function resolvePortable() {
@@ -32,8 +39,8 @@ async function resolvePortable() {
const zip = new AdmZip(); 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, "clash-meta.exe")); zip.addLocalFile(path.join(releaseDir, "verge-mihomo.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-meta-alpha.exe")); zip.addLocalFile(path.join(releaseDir, "verge-mihomo-alpha.exe"));
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources"); zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
zip.addLocalFolder(configDir, ".config"); zip.addLocalFolder(configDir, ".config");
@@ -41,7 +48,7 @@ async function resolvePortable() {
const packageJson = require("../package.json"); const packageJson = require("../package.json");
const { version } = packageJson; const { version } = packageJson;
const zipFile = `Clash.Verge_${version}_${ARCH_MAP[target]}_portable.zip`; const zipFile = `Clash.Verge_${version}_${arch}_portable.zip`;
zip.writeZip(zipFile); zip.writeZip(zipFile);
console.log("[INFO]: create portable zip successfully"); console.log("[INFO]: create portable zip successfully");
+157
View File
@@ -0,0 +1,157 @@
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog } from "./updatelog.mjs";
const UPDATE_TAG_NAME = "updater";
const UPDATE_JSON_FILE = "update-fixed-webview2.json";
const UPDATE_JSON_PROXY = "update-fixed-webview2-proxy.json";
/// generate update.json
/// upload to update tag's release asset
async function resolveUpdater() {
if (process.env.GITHUB_TOKEN === undefined) {
throw new Error("GITHUB_TOKEN is required");
}
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const { data: tags } = await github.rest.repos.listTags({
...options,
per_page: 10,
page: 1,
});
// get the latest publish tag
const tag = tags.find((t) => t.name.startsWith("v"));
console.log(tag);
console.log();
const { data: latestRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name), // use updatelog.md
pub_date: new Date().toISOString(),
platforms: {
"windows-x86_64": { signature: "", url: "" },
"windows-aarch64": { signature: "", url: "" },
"windows-x86": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" },
},
};
const promises = latestRelease.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
// win64 url
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip")) {
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith("x64_fixed_webview2-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip")) {
updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith("x86_fixed_webview2-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig;
}
// win arm url
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64_fixed_webview2-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
});
await Promise.allSettled(promises);
console.log(updateData);
// maybe should test the signature as well
// delete the null field
Object.entries(updateData.platforms).forEach(([key, value]) => {
if (!value.url) {
console.log(`[Error]: failed to parse release for "${key}"`);
delete updateData.platforms[key];
}
});
// 生成一个代理github的更新文件
// 使用 https://hub.fastgit.xyz/ 做github资源的加速
const updateDataNew = JSON.parse(JSON.stringify(updateData));
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
if (value.url) {
updateDataNew.platforms[key].url =
"https://mirror.ghproxy.com/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// update the update.json
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: UPDATE_TAG_NAME,
});
// delete the old assets
for (let asset of updateRelease.assets) {
if (asset.name === UPDATE_JSON_FILE) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === UPDATE_JSON_PROXY) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch(console.error); // do not break the pipeline
}
}
// upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_FILE,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: UPDATE_JSON_PROXY,
data: JSON.stringify(updateDataNew, null, 2),
});
}
// get the signature file content
async function getSignature(url) {
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/octet-stream" },
});
return response.text();
}
resolveUpdater().catch(console.error);
+25 -14
View File
@@ -45,10 +45,14 @@ async function resolveUpdater() {
"darwin-intel": { signature: "", url: "" }, "darwin-intel": { signature: "", url: "" },
"darwin-x86_64": { signature: "", url: "" }, "darwin-x86_64": { signature: "", url: "" },
"linux-x86_64": { signature: "", url: "" }, "linux-x86_64": { signature: "", url: "" },
"linux-x86": { signature: "", url: "" },
"linux-i686": { signature: "", url: "" },
"linux-aarch64": { signature: "", url: "" }, "linux-aarch64": { signature: "", url: "" },
"linux-armv7": { signature: "", url: "" }, "linux-armv7": { signature: "", url: "" },
"windows-x86_64": { signature: "", url: "" }, "windows-x86_64": { signature: "", url: "" },
"windows-aarch64": { signature: "", url: "" }, "windows-aarch64": { signature: "", url: "" },
"windows-x86": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" },
}, },
}; };
@@ -67,6 +71,18 @@ async function resolveUpdater() {
updateData.platforms["windows-x86_64"].signature = sig; updateData.platforms["windows-x86_64"].signature = sig;
} }
// win32 url
if (name.endsWith("x64-setup.nsis.zip")) {
updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith("x64-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig;
}
// win arm url // win arm url
if (name.endsWith("arm64-setup.nsis.zip")) { if (name.endsWith("arm64-setup.nsis.zip")) {
updateData.platforms["windows-aarch64"].url = browser_download_url; updateData.platforms["windows-aarch64"].url = browser_download_url;
@@ -94,27 +110,22 @@ async function resolveUpdater() {
// darwin url (aarch) // darwin url (aarch)
if (name.endsWith("aarch64.app.tar.gz")) { if (name.endsWith("aarch64.app.tar.gz")) {
updateData.platforms["darwin-aarch64"].url = browser_download_url; updateData.platforms["darwin-aarch64"].url = browser_download_url;
// 使linux可以检查更新
updateData.platforms.linux.url = browser_download_url;
updateData.platforms["linux-x86_64"].url = browser_download_url;
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].url = browser_download_url;
updateData.platforms["linux-armv7"].url = browser_download_url;
} }
// darwin signature (aarch) // darwin signature (aarch)
if (name.endsWith("aarch64.app.tar.gz.sig")) { if (name.endsWith("aarch64.app.tar.gz.sig")) {
const sig = await getSignature(browser_download_url); const sig = await getSignature(browser_download_url);
updateData.platforms["darwin-aarch64"].signature = sig; updateData.platforms["darwin-aarch64"].signature = sig;
}
// linux x64 url
if (name.endsWith("amd64.AppImage.tar.gz")) {
updateData.platforms.linux.url = browser_download_url;
updateData.platforms["linux-x86_64"].url = browser_download_url;
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
updateData.platforms["linux-aarch64"].url = browser_download_url;
updateData.platforms["linux-armv7"].url = browser_download_url;
}
// linux x64 signature
if (name.endsWith("amd64.AppImage.tar.gz.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.linux.signature = sig; updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig; updateData.platforms["linux-x86_64"].signature = sig;
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].signature = sig; updateData.platforms["linux-aarch64"].signature = sig;
updateData.platforms["linux-armv7"].signature = sig; updateData.platforms["linux-armv7"].signature = sig;
} }
+759 -598
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "clash-verge" name = "clash-verge"
version = "1.6.1" version = "1.7.4"
description = "clash verge" description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"] authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only" license = "GPL-3.0-only"
@@ -10,13 +10,13 @@ edition = "2021"
build = "build.rs" build = "build.rs"
[build-dependencies] [build-dependencies]
tauri-build = { version = "1", features = [] } tauri-build = { version="1", features = [] }
[dependencies] [dependencies]
warp = "0.3" warp = "0.3"
anyhow = "1.0" anyhow = "1.0"
dirs = "5.0" dirs = "5.0"
open = "5.0" open = "5.1"
log = "0.4" log = "0.4"
dunce = "1.0" dunce = "1.0"
log4rs = "1" log4rs = "1"
@@ -26,19 +26,19 @@ sysinfo = "0.30"
boa_engine = "0.18" boa_engine = "0.18"
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.9" serde_yaml = "0.9"
once_cell = "1.18" once_cell = "1.19"
port_scanner = "0.1.5" port_scanner = "0.1.5"
delay_timer = "0.11.5" delay_timer = "0.11"
parking_lot = "0.12" parking_lot = "0.12"
auto-launch = "0.5.0"
percent-encoding = "2.3.1" percent-encoding = "2.3.1"
window-shadows = { version = "0.2" } window-shadows = { version = "0.2" }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] } reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" } sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" } tauri = { version="1", features = [ "fs-read-file", "fs-exists", "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "icon-ico", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] }
tauri = { version = "1.6", features = [ "fs-read-file", "fs-exists", "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "icon-ico", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] } network-interface = { version = "2.0.0", features = ["serde"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
runas = "=1.2.0" runas = "=1.2.0"
deelevate = "0.2.0" deelevate = "0.2.0"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+51 -12
View File
@@ -6,12 +6,19 @@ use crate::{
}; };
use crate::{ret_err, wrap_err}; use crate::{ret_err, wrap_err};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use network_interface::NetworkInterface;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use sysproxy::Sysproxy; use sysproxy::{Autoproxy, Sysproxy};
use tauri::{api, Manager}; use tauri::{api, Manager};
type CmdResult<T = ()> = Result<T, String>; type CmdResult<T = ()> = Result<T, String>;
#[tauri::command]
pub fn copy_clash_env(app_handle: tauri::AppHandle) -> CmdResult {
feat::copy_clash_env(&app_handle);
Ok(())
}
#[tauri::command] #[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> { pub fn get_profiles() -> CmdResult<IProfiles> {
Ok(Config::profiles().data().clone()) Ok(Config::profiles().data().clone())
@@ -181,20 +188,10 @@ pub async fn restart_sidecar() -> CmdResult {
wrap_err!(CoreManager::global().run_core().await) wrap_err!(CoreManager::global().run_core().await)
} }
#[tauri::command]
pub fn grant_permission(_core: String) -> CmdResult {
#[cfg(any(target_os = "macos", target_os = "linux"))]
return wrap_err!(manager::grant_permission(_core));
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
return Err("Unsupported target".into());
}
/// get the system proxy /// get the system proxy
#[tauri::command] #[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> { pub fn get_sys_proxy() -> CmdResult<Mapping> {
let current = wrap_err!(Sysproxy::get_system_proxy())?; let current = wrap_err!(Sysproxy::get_system_proxy())?;
let mut map = Mapping::new(); let mut map = Mapping::new();
map.insert("enable".into(), current.enable.into()); map.insert("enable".into(), current.enable.into());
map.insert( map.insert(
@@ -206,6 +203,18 @@ pub fn get_sys_proxy() -> CmdResult<Mapping> {
Ok(map) 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] #[tauri::command]
pub fn get_clash_logs() -> CmdResult<VecDeque<String>> { pub fn get_clash_logs() -> CmdResult<VecDeque<String>> {
Ok(logger::Logger::global().get_log()) Ok(logger::Logger::global().get_log())
@@ -316,10 +325,40 @@ pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
Err(err) => Err(err.to_string()), Err(err) => Err(err.to_string()),
} }
} else { } else {
return Err("file not found".to_string()); 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());
}
return 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] #[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) { pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_window("main") { if let Some(window) = app_handle.get_window("main") {
+2 -2
View File
@@ -13,7 +13,7 @@ pub struct IClashTemp(pub Mapping);
impl IClashTemp { impl IClashTemp {
pub fn new() -> Self { pub fn new() -> Self {
let template = Self::template(); let template = Self::template();
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) { match dirs::clash_path().and_then(|path| help::read_mapping(&path)) {
Ok(mut map) => { Ok(mut map) => {
template.0.keys().for_each(|key| { template.0.keys().for_each(|key| {
if !map.contains_key(key) { if !map.contains_key(key) {
@@ -33,7 +33,7 @@ impl IClashTemp {
let mut map = Mapping::new(); let mut map = Mapping::new();
let mut tun = Mapping::new(); let mut tun = Mapping::new();
tun.insert("stack".into(), "gvisor".into()); tun.insert("stack".into(), "gvisor".into());
tun.insert("device".into(), "Meta".into()); tun.insert("device".into(), "Mihomo".into());
tun.insert("auto-route".into(), true.into()); tun.insert("auto-route".into(), true.into());
tun.insert("strict-route".into(), false.into()); tun.insert("strict-route".into(), false.into());
tun.insert("auto-detect-interface".into(), true.into()); tun.insert("auto-detect-interface".into(), true.into());
+21 -4
View File
@@ -1,5 +1,6 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge}; use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{ use crate::{
config::PrfItem,
enhance, enhance,
utils::{dirs, help}, utils::{dirs, help},
}; };
@@ -46,8 +47,24 @@ impl Config {
} }
/// 初始化订阅 /// 初始化订阅
pub fn init_config() -> Result<()> { pub async fn init_config() -> Result<()> {
crate::log_err!(Self::generate()); if Self::profiles()
.data()
.get_item(&"Merge".to_string())
.is_err()
{
let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?;
Self::profiles().data().append_item(merge_item.clone())?;
}
if Self::profiles()
.data()
.get_item(&"Script".to_string())
.is_err()
{
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
Self::profiles().data().append_item(script_item.clone())?;
}
crate::log_err!(Self::generate().await);
if let Err(err) = Self::generate_file(ConfigType::Run) { if let Err(err) = Self::generate_file(ConfigType::Run) {
log::error!(target: "app", "{err}"); log::error!(target: "app", "{err}");
@@ -83,8 +100,8 @@ impl Config {
} }
/// 生成订阅存好 /// 生成订阅存好
pub fn generate() -> Result<()> { pub async fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance(); let (config, exists_keys, logs) = enhance::enhance().await;
*Config::runtime().draft() = IRuntime { *Config::runtime().draft() = IRuntime {
config: Some(config), config: Some(config),
+6
View File
@@ -1,4 +1,5 @@
mod clash; mod clash;
#[allow(clippy::module_inception)]
mod config; mod config;
mod draft; mod draft;
mod prfitem; mod prfitem;
@@ -13,3 +14,8 @@ pub use self::prfitem::*;
pub use self::profiles::*; pub use self::profiles::*;
pub use self::runtime::*; pub use self::runtime::*;
pub use self::verge::*; pub use self::verge::*;
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
}
"#;
+202 -42
View File
@@ -94,6 +94,16 @@ pub struct PrfOption {
/// default is `false` /// default is `false`
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub danger_accept_invalid_certs: Option<bool>, pub danger_accept_invalid_certs: Option<bool>,
pub merge: Option<String>,
pub script: Option<String>,
pub rules: Option<String>,
pub proxies: Option<String>,
pub groups: Option<String>,
} }
impl PrfOption { impl PrfOption {
@@ -103,8 +113,15 @@ impl PrfOption {
a.user_agent = b.user_agent.or(a.user_agent); a.user_agent = b.user_agent.or(a.user_agent);
a.with_proxy = b.with_proxy.or(a.with_proxy); a.with_proxy = b.with_proxy.or(a.with_proxy);
a.self_proxy = b.self_proxy.or(a.self_proxy); a.self_proxy = b.self_proxy.or(a.self_proxy);
a.danger_accept_invalid_certs = b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs); a.danger_accept_invalid_certs = b
.danger_accept_invalid_certs
.or(a.danger_accept_invalid_certs);
a.update_interval = b.update_interval.or(a.update_interval); a.update_interval = b.update_interval.or(a.update_interval);
a.merge = b.merge.or(a.merge);
a.script = b.script.or(a.script);
a.rules = b.rules.or(a.rules);
a.proxies = b.proxies.or(a.proxies);
a.groups = b.groups.or(a.groups);
Some(a) Some(a)
} }
t => t.0.or(t.1), t => t.0.or(t.1),
@@ -133,17 +150,7 @@ impl PrfItem {
"local" => { "local" => {
let name = item.name.unwrap_or("Local File".into()); let name = item.name.unwrap_or("Local File".into());
let desc = item.desc.unwrap_or("".into()); let desc = item.desc.unwrap_or("".into());
PrfItem::from_local(name, desc, file_data) PrfItem::from_local(name, desc, file_data, item.option)
}
"merge" => {
let name = item.name.unwrap_or("Merge".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_merge(name, desc)
}
"script" => {
let name = item.name.unwrap_or("Script".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_script(name, desc)
} }
typ => bail!("invalid profile item type \"{typ}\""), typ => bail!("invalid profile item type \"{typ}\""),
} }
@@ -151,10 +158,49 @@ impl PrfItem {
/// ## Local type /// ## Local type
/// create a new item from name/desc /// create a new item from name/desc
pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> { pub fn from_local(
let uid = help::get_uid("l"); name: String,
desc: String,
file_data: Option<String>,
option: Option<PrfOption>,
) -> Result<PrfItem> {
let uid = help::get_uid("L");
let file = format!("{uid}.yaml"); let file = format!("{uid}.yaml");
let opt_ref = option.as_ref();
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone());
let mut script = opt_ref.and_then(|o| o.script.clone());
let mut rules = opt_ref.and_then(|o| o.rules.clone());
let mut proxies = opt_ref.and_then(|o| o.proxies.clone());
let mut groups = opt_ref.and_then(|o| o.groups.clone());
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles().data().append_item(rules_item.clone())?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data()
.append_item(proxies_item.clone())?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles().data().append_item(groups_item.clone())?;
groups = groups_item.uid;
}
Ok(PrfItem { Ok(PrfItem {
uid: Some(uid), uid: Some(uid),
itype: Some("local".into()), itype: Some("local".into()),
@@ -164,7 +210,15 @@ impl PrfItem {
url: None, url: None,
selected: None, selected: None,
extra: None, extra: None,
option: None, option: Some(PrfOption {
update_interval,
merge,
script,
rules,
proxies,
groups,
..PrfOption::default()
}),
home: None, home: None,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())), file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
@@ -182,10 +236,15 @@ impl PrfItem {
let opt_ref = option.as_ref(); let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false)); let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false)); let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let accept_invalid_certs = opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false)); let accept_invalid_certs =
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone()); let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval); let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone());
let mut script = opt_ref.and_then(|o| o.script.clone());
let mut rules = opt_ref.and_then(|o| o.rules.clone());
let mut proxies = opt_ref.and_then(|o| o.proxies.clone());
let mut groups = opt_ref.and_then(|o| o.groups.clone());
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy(); let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
// 使用软件自己的代理 // 使用软件自己的代理
@@ -279,17 +338,11 @@ impl PrfItem {
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()), crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
), ),
}; };
let option = match update_interval { let update_interval = match update_interval {
Some(val) => Some(PrfOption { Some(val) => Some(val),
update_interval: Some(val),
..PrfOption::default()
}),
None => match header.get("profile-update-interval") { None => match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() { Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption { Ok(val) => Some(val * 60), // hour -> min
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Err(_) => None, Err(_) => None,
}, },
None => None, None => None,
@@ -300,11 +353,11 @@ impl PrfItem {
Some(value) => { Some(value) => {
let str_value = value.to_str().unwrap_or(""); let str_value = value.to_str().unwrap_or("");
Some(str_value.to_string()) Some(str_value.to_string())
}, }
None => None, None => None,
}; };
let uid = help::get_uid("r"); let uid = help::get_uid("R");
let file = format!("{uid}.yaml"); let file = format!("{uid}.yaml");
let name = name.unwrap_or(filename.unwrap_or("Remote File".into())); let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
let data = resp.text_with_charset("utf-8").await?; let data = resp.text_with_charset("utf-8").await?;
@@ -320,6 +373,34 @@ impl PrfItem {
bail!("profile does not contain `proxies` or `proxy-providers`"); bail!("profile does not contain `proxies` or `proxy-providers`");
} }
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles().data().append_item(rules_item.clone())?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data()
.append_item(proxies_item.clone())?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles().data().append_item(groups_item.clone())?;
groups = groups_item.uid;
}
Ok(PrfItem { Ok(PrfItem {
uid: Some(uid), uid: Some(uid),
itype: Some("remote".into()), itype: Some("remote".into()),
@@ -329,7 +410,15 @@ impl PrfItem {
url: Some(url.into()), url: Some(url.into()),
selected: None, selected: None,
extra, extra,
option, option: Some(PrfOption {
update_interval,
merge,
script,
rules,
proxies,
groups,
..PrfOption::default()
}),
home, home,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(data.into()), file_data: Some(data.into()),
@@ -338,15 +427,20 @@ impl PrfItem {
/// ## Merge type (enhance) /// ## Merge type (enhance)
/// create the enhanced item by using `merge` rule /// create the enhanced item by using `merge` rule
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> { pub fn from_merge(uid: Option<String>) -> Result<PrfItem> {
let uid = help::get_uid("m"); let mut id = help::get_uid("m");
let file = format!("{uid}.yaml"); let mut template = tmpl::ITEM_MERGE_EMPTY.into();
if let Some(uid) = uid {
id = uid;
template = tmpl::ITEM_MERGE.into();
}
let file = format!("{id}.yaml");
Ok(PrfItem { Ok(PrfItem {
uid: Some(uid), uid: Some(id),
itype: Some("merge".into()), itype: Some("merge".into()),
name: Some(name), name: None,
desc: Some(desc), desc: None,
file: Some(file), file: Some(file),
url: None, url: None,
selected: None, selected: None,
@@ -354,21 +448,24 @@ impl PrfItem {
option: None, option: None,
home: None, home: None,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_MERGE.into()), file_data: Some(template),
}) })
} }
/// ## Script type (enhance) /// ## Script type (enhance)
/// create the enhanced item by using javascript quick.js /// create the enhanced item by using javascript quick.js
pub fn from_script(name: String, desc: String) -> Result<PrfItem> { pub fn from_script(uid: Option<String>) -> Result<PrfItem> {
let uid = help::get_uid("s"); let mut id = help::get_uid("s");
let file = format!("{uid}.js"); // js ext if let Some(uid) = uid {
id = uid;
}
let file = format!("{id}.js"); // js ext
Ok(PrfItem { Ok(PrfItem {
uid: Some(uid), uid: Some(id),
itype: Some("script".into()), itype: Some("script".into()),
name: Some(name), name: None,
desc: Some(desc), desc: None,
file: Some(file), file: Some(file),
url: None, url: None,
home: None, home: None,
@@ -380,6 +477,69 @@ impl PrfItem {
}) })
} }
/// ## Rules type (enhance)
pub fn from_rules() -> Result<PrfItem> {
let uid = help::get_uid("r");
let file = format!("{uid}.yaml"); // yaml ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("rules".into()),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_RULES.into()),
})
}
/// ## Proxies type (enhance)
pub fn from_proxies() -> Result<PrfItem> {
let uid = help::get_uid("p");
let file = format!("{uid}.yaml"); // yaml ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("proxies".into()),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_PROXIES.into()),
})
}
/// ## Groups type (enhance)
pub fn from_groups() -> Result<PrfItem> {
let uid = help::get_uid("g");
let file = format!("{uid}.yaml"); // yaml ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("groups".into()),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_GROUPS.into()),
})
}
/// get the file data /// get the file data
pub fn read_file(&self) -> Result<String> { pub fn read_file(&self) -> Result<String> {
if self.file.is_none() { if self.file.is_none() {
+169 -12
View File
@@ -1,4 +1,4 @@
use super::prfitem::PrfItem; use super::{prfitem::PrfItem, PrfOption};
use crate::utils::{dirs, help}; use crate::utils::{dirs, help};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -11,9 +11,6 @@ pub struct IProfiles {
/// same as PrfConfig.current /// same as PrfConfig.current
pub current: Option<String>, pub current: Option<String>,
/// same as PrfConfig.chain
pub chain: Option<Vec<String>>,
/// profile list /// profile list
pub items: Option<Vec<PrfItem>>, pub items: Option<Vec<PrfItem>>,
} }
@@ -80,10 +77,6 @@ impl IProfiles {
} }
} }
if let Some(chain) = patch.chain {
self.chain = Some(chain);
}
Ok(()) Ok(())
} }
@@ -212,6 +205,7 @@ impl IProfiles {
each.extra = item.extra; each.extra = item.extra;
each.updated = item.updated; each.updated = item.updated;
each.home = item.home; each.home = item.home;
each.option = PrfOption::merge(each.option.clone(), item.option);
// save the file data // save the file data
// move the field value after save // move the field value after save
if let Some(file_data) = item.file_data.take() { if let Some(file_data) = item.file_data.take() {
@@ -243,9 +237,19 @@ impl IProfiles {
pub fn delete_item(&mut self, uid: String) -> Result<bool> { pub fn delete_item(&mut self, uid: String) -> Result<bool> {
let current = self.current.as_ref().unwrap_or(&uid); let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone(); let current = current.clone();
let item = self.get_item(&uid)?;
let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
let mut items = self.items.take().unwrap_or_default(); let mut items = self.items.take().unwrap_or_default();
let mut index = None; let mut index = None;
let mut merge_index = None;
let mut script_index = None;
let mut rules_index = None;
let mut proxies_index = None;
let mut groups_index = None;
// get the index // get the index
for (i, _) in items.iter().enumerate() { for (i, _) in items.iter().enumerate() {
@@ -254,7 +258,6 @@ impl IProfiles {
break; break;
} }
} }
if let Some(index) = index { if let Some(index) = index {
if let Some(file) = items.remove(index).file { if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| { let _ = dirs::app_profiles_dir().map(|path| {
@@ -265,7 +268,91 @@ impl IProfiles {
}); });
} }
} }
// get the merge index
for (i, _) in items.iter().enumerate() {
if items[i].uid == merge_uid {
merge_index = Some(i);
break;
}
}
if let Some(index) = merge_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the script index
for (i, _) in items.iter().enumerate() {
if items[i].uid == script_uid {
script_index = Some(i);
break;
}
}
if let Some(index) = script_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the rules index
for (i, _) in items.iter().enumerate() {
if items[i].uid == rules_uid {
rules_index = Some(i);
break;
}
}
if let Some(index) = rules_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the proxies index
for (i, _) in items.iter().enumerate() {
if items[i].uid == proxies_uid {
proxies_index = Some(i);
break;
}
}
if let Some(index) = proxies_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// get the groups index
for (i, _) in items.iter().enumerate() {
if items[i].uid == groups_uid {
groups_index = Some(i);
break;
}
}
if let Some(index) = groups_index {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
}
}
// delete the original uid // delete the original uid
if current == uid { if current == uid {
self.current = match !items.is_empty() { self.current = match !items.is_empty() {
@@ -288,11 +375,81 @@ impl IProfiles {
Some(file) => dirs::app_profiles_dir()?.join(file), Some(file) => dirs::app_profiles_dir()?.join(file),
None => bail!("failed to get the file field"), None => bail!("failed to get the file field"),
}; };
return help::read_merge_mapping(&file_path); return help::read_mapping(&file_path);
} }
bail!("failed to find the current profile \"uid:{current}\""); bail!("failed to find the current profile \"uid:{current}\"");
} }
_ => Ok(Mapping::new()), _ => Ok(Mapping::new()),
} }
} }
/// 获取current指向的订阅的merge
pub fn current_merge(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let merge = item.option.as_ref().and_then(|e| e.merge.clone());
return merge;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的script
pub fn current_script(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let script = item.option.as_ref().and_then(|e| e.script.clone());
return script;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的rules
pub fn current_rules(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let rules = item.option.as_ref().and_then(|e| e.rules.clone());
return rules;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的proxies
pub fn current_proxies(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let proxies = item.option.as_ref().and_then(|e| e.proxies.clone());
return proxies;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的groups
pub fn current_groups(&self) -> Option<String> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
let groups = item.option.as_ref().and_then(|e| e.groups.clone());
return groups;
}
None
}
_ => None,
}
}
} }
+47 -1
View File
@@ -1,3 +1,4 @@
use crate::config::DEFAULT_PAC;
use crate::utils::{dirs, help}; use crate::utils::{dirs, help};
use anyhow::Result; use anyhow::Result;
use log::LevelFilter; use log::LevelFilter;
@@ -42,6 +43,10 @@ pub struct IVerge {
/// common tray icon /// common tray icon
pub common_tray_icon: Option<bool>, pub common_tray_icon: Option<bool>,
/// tray icon
#[cfg(target_os = "macos")]
pub tray_icon: Option<String>,
/// menu icon /// menu icon
pub menu_icon: Option<String>, pub menu_icon: Option<String>,
@@ -70,12 +75,21 @@ pub struct IVerge {
/// enable proxy guard /// enable proxy guard
pub enable_proxy_guard: Option<bool>, pub enable_proxy_guard: Option<bool>,
/// always use default bypass
pub use_default_bypass: Option<bool>,
/// set system proxy bypass /// set system proxy bypass
pub system_proxy_bypass: Option<String>, pub system_proxy_bypass: Option<String>,
/// proxy guard duration /// proxy guard duration
pub proxy_guard_duration: Option<u64>, pub proxy_guard_duration: Option<u64>,
/// use pac mode
pub proxy_auto_config: Option<bool>,
/// pac script content
pub pac_file_content: Option<String>,
/// theme setting /// theme setting
pub theme_setting: Option<IVergeTheme>, pub theme_setting: Option<IVergeTheme>,
@@ -130,14 +144,24 @@ pub struct IVerge {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub verge_redir_port: Option<u16>, pub verge_redir_port: Option<u16>,
#[cfg(not(target_os = "windows"))]
pub verge_redir_enabled: Option<bool>,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub verge_tproxy_port: Option<u16>, pub verge_tproxy_port: Option<u16>,
#[cfg(target_os = "linux")]
pub verge_tproxy_enabled: Option<bool>,
pub verge_mixed_port: Option<u16>, pub verge_mixed_port: Option<u16>,
pub verge_socks_port: Option<u16>, pub verge_socks_port: Option<u16>,
pub verge_socks_enabled: Option<bool>,
pub verge_port: Option<u16>, pub verge_port: Option<u16>,
pub verge_http_enabled: Option<bool>,
} }
#[derive(Default, Debug, Clone, Deserialize, Serialize)] #[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -177,7 +201,7 @@ impl IVerge {
pub fn template() -> Self { pub fn template() -> Self {
Self { Self {
clash_core: Some("clash-meta".into()), clash_core: Some("verge-mihomo".into()),
language: Some("zh".into()), language: Some("zh".into()),
theme_mode: Some("system".into()), theme_mode: Some("system".into()),
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
@@ -188,6 +212,8 @@ impl IVerge {
traffic_graph: Some(true), traffic_graph: Some(true),
enable_memory_usage: Some(true), enable_memory_usage: Some(true),
enable_group_icon: Some(true), enable_group_icon: Some(true),
#[cfg(target_os = "macos")]
tray_icon: Some("monochrome".into()),
menu_icon: Some("monochrome".into()), menu_icon: Some("monochrome".into()),
common_tray_icon: Some(false), common_tray_icon: Some(false),
sysproxy_tray_icon: Some(false), sysproxy_tray_icon: Some(false),
@@ -195,15 +221,24 @@ impl IVerge {
enable_auto_launch: Some(false), enable_auto_launch: Some(false),
enable_silent_start: Some(false), enable_silent_start: Some(false),
enable_system_proxy: Some(false), enable_system_proxy: Some(false),
proxy_auto_config: Some(false),
pac_file_content: Some(DEFAULT_PAC.into()),
enable_random_port: Some(false), enable_random_port: Some(false),
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
verge_redir_port: Some(7895), verge_redir_port: Some(7895),
#[cfg(not(target_os = "windows"))]
verge_redir_enabled: Some(false),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
verge_tproxy_port: Some(7896), verge_tproxy_port: Some(7896),
#[cfg(target_os = "linux")]
verge_tproxy_enabled: Some(false),
verge_mixed_port: Some(7897), verge_mixed_port: Some(7897),
verge_socks_port: Some(7898), verge_socks_port: Some(7898),
verge_socks_enabled: Some(false),
verge_port: Some(7899), verge_port: Some(7899),
verge_http_enabled: Some(false),
enable_proxy_guard: Some(false), enable_proxy_guard: Some(false),
use_default_bypass: Some(true),
proxy_guard_duration: Some(30), proxy_guard_duration: Some(30),
auto_close_connection: Some(true), auto_close_connection: Some(true),
auto_check_update: Some(true), auto_check_update: Some(true),
@@ -239,6 +274,8 @@ impl IVerge {
patch!(traffic_graph); patch!(traffic_graph);
patch!(enable_memory_usage); patch!(enable_memory_usage);
patch!(enable_group_icon); patch!(enable_group_icon);
#[cfg(target_os = "macos")]
patch!(tray_icon);
patch!(menu_icon); patch!(menu_icon);
patch!(common_tray_icon); patch!(common_tray_icon);
patch!(sysproxy_tray_icon); patch!(sysproxy_tray_icon);
@@ -251,15 +288,24 @@ impl IVerge {
patch!(enable_random_port); patch!(enable_random_port);
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
patch!(verge_redir_port); patch!(verge_redir_port);
#[cfg(not(target_os = "windows"))]
patch!(verge_redir_enabled);
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
patch!(verge_tproxy_port); patch!(verge_tproxy_port);
#[cfg(target_os = "linux")]
patch!(verge_tproxy_enabled);
patch!(verge_mixed_port); patch!(verge_mixed_port);
patch!(verge_socks_port); patch!(verge_socks_port);
patch!(verge_socks_enabled);
patch!(verge_port); patch!(verge_port);
patch!(verge_http_enabled);
patch!(enable_system_proxy); patch!(enable_system_proxy);
patch!(enable_proxy_guard); patch!(enable_proxy_guard);
patch!(use_default_bypass);
patch!(system_proxy_bypass); patch!(system_proxy_bypass);
patch!(proxy_guard_duration); patch!(proxy_guard_duration);
patch!(proxy_auto_config);
patch!(pac_file_content);
patch!(theme_setting); patch!(theme_setting);
patch!(web_ui_list); patch!(web_ui_list);
+1
View File
@@ -85,6 +85,7 @@ fn clash_client_info() -> Result<(String, HeaderMap)> {
} }
/// 缩短clash的日志 /// 缩短clash的日志
#[allow(dead_code)]
pub fn parse_log(log: String) -> String { pub fn parse_log(log: String) -> String {
if log.starts_with("time=") && log.len() > 33 { if log.starts_with("time=") && log.len() > 33 {
return (log[33..]).to_owned(); return (log[33..]).to_owned();
+91 -89
View File
@@ -1,12 +1,13 @@
use super::service; use crate::config::*;
use super::{clash_api, logger::Logger}; use crate::core::{clash_api, handle, logger::Logger, service};
use crate::log_err; use crate::log_err;
use crate::{config::*, utils::dirs}; use crate::utils::dirs;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{fs, io::Write, sync::Arc, time::Duration}; use serde_yaml::Mapping;
use sysinfo::{Pid, System}; use std::{sync::Arc, time::Duration};
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
use tauri::api::process::{Command, CommandChild, CommandEvent}; use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep; use tokio::time::sleep;
@@ -29,21 +30,6 @@ impl CoreManager {
} }
pub fn init(&self) -> Result<()> { pub fn init(&self) -> Result<()> {
// kill old clash process
let _ = dirs::clash_pid_path()
.and_then(|path| fs::read(path).map(|p| p.to_vec()).context(""))
.and_then(|pid| String::from_utf8_lossy(&pid).parse().context(""))
.map(|pid| {
let mut system = System::new();
system.refresh_all();
if let Some(proc) = system.process(Pid::from_u32(pid)) {
if proc.name().contains("clash") {
log::debug!(target: "app", "kill old clash process");
proc.kill();
}
}
});
tauri::async_runtime::spawn(async { tauri::async_runtime::spawn(async {
// 启动clash // 启动clash
log_err!(Self::global().run_core().await); log_err!(Self::global().run_core().await);
@@ -58,13 +44,27 @@ impl CoreManager {
let config_path = dirs::path_to_str(&config_path)?; let config_path = dirs::path_to_str(&config_path)?;
let clash_core = { Config::verge().latest().clash_core.clone() }; let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into()); let mut clash_core = clash_core.unwrap_or("verge-mihomo".into());
let app_dir = dirs::app_home_dir()?; // compatibility
let app_dir = dirs::path_to_str(&app_dir)?; if clash_core.contains("clash") {
clash_core = "verge-mihomo".to_string();
Config::verge().draft().patch_config(IVerge {
clash_core: Some("verge-mihomo".to_string()),
..IVerge::default()
});
Config::verge().apply();
match Config::verge().data().save_file() {
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
}
let test_dir = dirs::app_home_dir()?.join("test");
let test_dir = dirs::path_to_str(&test_dir)?;
let output = Command::new_sidecar(clash_core)? let output = Command::new_sidecar(clash_core)?
.args(["-t", "-d", app_dir, "-f", config_path]) .args(["-t", "-d", test_dir, "-f", config_path])
.output()?; .output()?;
if !output.status.success() { if !output.status.success() {
@@ -84,25 +84,27 @@ impl CoreManager {
pub async fn run_core(&self) -> Result<()> { pub async fn run_core(&self) -> Result<()> {
let config_path = Config::generate_file(ConfigType::Run)?; let config_path = Config::generate_file(ConfigType::Run)?;
#[allow(unused_mut)] // 关闭tun模式
let mut should_kill = match self.sidecar.lock().take() { let mut disable = Mapping::new();
Some(child) => { let mut tun = Mapping::new();
log::debug!(target: "app", "stop the core by sidecar"); tun.insert("enable".into(), false.into());
let _ = child.kill(); disable.insert("tun".into(), tun.into());
true log::debug!(target: "app", "disable tun mode");
} let _ = clash_api::patch_configs(&disable).await;
None => false,
};
if *self.use_service_mode.lock() { if *self.use_service_mode.lock() {
log::debug!(target: "app", "stop the core by service"); log::debug!(target: "app", "stop the core by service");
log_err!(service::stop_core_by_service().await); log_err!(service::stop_core_by_service().await);
should_kill = true; } else {
} let system = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
);
let procs = system.processes_by_name("verge-mihomo");
// 这里得等一会儿 for proc in procs {
if should_kill { log::debug!(target: "app", "kill all clash process");
sleep(Duration::from_millis(500)).await; proc.kill();
}
} }
// 服务模式 // 服务模式
@@ -115,12 +117,12 @@ impl CoreManager {
// 服务模式启动失败就直接运行sidecar // 服务模式启动失败就直接运行sidecar
log::debug!(target: "app", "try to run core in service mode"); log::debug!(target: "app", "try to run core in service mode");
match (|| async { let res = async {
service::check_service().await?; service::check_service().await?;
service::run_core_by_service(&config_path).await service::run_core_by_service(&config_path).await
})() }
.await .await;
{ match res {
Ok(_) => return Ok(()), Ok(_) => return Ok(()),
Err(err) => { Err(err) => {
// 修改这个值,免得stop出错 // 修改这个值,免得stop出错
@@ -134,31 +136,29 @@ impl CoreManager {
let app_dir = dirs::path_to_str(&app_dir)?; let app_dir = dirs::path_to_str(&app_dir)?;
let clash_core = { Config::verge().latest().clash_core.clone() }; let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into()); let mut clash_core = clash_core.unwrap_or("verge-mihomo".into());
let is_clash = clash_core == "clash";
// compatibility
if clash_core.contains("clash") {
clash_core = "verge-mihomo".to_string();
Config::verge().draft().patch_config(IVerge {
clash_core: Some("verge-mihomo".to_string()),
..IVerge::default()
});
Config::verge().apply();
match Config::verge().data().save_file() {
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
}
let config_path = dirs::path_to_str(&config_path)?; let config_path = dirs::path_to_str(&config_path)?;
let args = match clash_core.as_str() { let args = vec!["-d", app_dir, "-f", config_path];
"clash-meta" => vec!["-d", app_dir, "-f", config_path],
"clash-meta-alpha" => vec!["-d", app_dir, "-f", config_path],
_ => vec!["-d", app_dir, "-f", config_path],
};
let cmd = Command::new_sidecar(clash_core)?; let cmd = Command::new_sidecar(clash_core)?;
let (mut rx, cmd_child) = cmd.args(args).spawn()?; let (mut rx, cmd_child) = cmd.args(args).spawn()?;
// 将pid写入文件中
crate::log_err!((|| {
let pid = cmd_child.pid();
let path = dirs::clash_pid_path()?;
fs::File::create(path)
.context("failed to create the pid file")?
.write(format!("{pid}").as_bytes())
.context("failed to write pid to the file")?;
<Result<()>>::Ok(())
})());
let mut sidecar = self.sidecar.lock(); let mut sidecar = self.sidecar.lock();
*sidecar = Some(cmd_child); *sidecar = Some(cmd_child);
drop(sidecar); drop(sidecar);
@@ -167,25 +167,19 @@ impl CoreManager {
while let Some(event) = rx.recv().await { while let Some(event) = rx.recv().await {
match event { match event {
CommandEvent::Stdout(line) => { CommandEvent::Stdout(line) => {
if is_clash { log::info!(target: "app", "[mihomo]: {line}");
let stdout = clash_api::parse_log(line.clone());
log::info!(target: "app", "[clash]: {stdout}");
} else {
log::info!(target: "app", "[clash]: {line}");
};
Logger::global().set_log(line); Logger::global().set_log(line);
} }
CommandEvent::Stderr(err) => { CommandEvent::Stderr(err) => {
// let stdout = clash_api::parse_log(err.clone()); log::error!(target: "app", "[mihomo]: {err}");
log::error!(target: "app", "[clash]: {err}");
Logger::global().set_log(err); Logger::global().set_log(err);
} }
CommandEvent::Error(err) => { CommandEvent::Error(err) => {
log::error!(target: "app", "[clash]: {err}"); log::error!(target: "app", "[mihomo]: {err}");
Logger::global().set_log(err); Logger::global().set_log(err);
} }
CommandEvent::Terminated(_) => { CommandEvent::Terminated(_) => {
log::info!(target: "app", "clash core terminated"); log::info!(target: "app", "mihomo core terminated");
let _ = CoreManager::global().recover_core(); let _ = CoreManager::global().recover_core();
break; break;
} }
@@ -205,9 +199,7 @@ impl CoreManager {
} }
// 清空原来的sidecar值 // 清空原来的sidecar值
if let Some(sidecar) = self.sidecar.lock().take() { let _ = self.sidecar.lock().take();
let _ = sidecar.kill();
}
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
// 6秒之后再查看服务是否正常 (时间随便搞的) // 6秒之后再查看服务是否正常 (时间随便搞的)
@@ -231,19 +223,31 @@ impl CoreManager {
} }
/// 停止核心运行 /// 停止核心运行
pub fn stop_core(&self) -> Result<()> { pub async fn stop_core(&self) -> Result<()> {
// 关闭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");
let _ = clash_api::patch_configs(&disable).await;
if *self.use_service_mode.lock() { if *self.use_service_mode.lock() {
log::debug!(target: "app", "stop the core by service"); log::debug!(target: "app", "stop the core by service");
tauri::async_runtime::block_on(async move { log_err!(service::stop_core_by_service().await);
log_err!(service::stop_core_by_service().await);
});
return Ok(()); return Ok(());
} }
let mut sidecar = self.sidecar.lock(); let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() { let _ = sidecar.take();
log::debug!(target: "app", "stop the core by sidecar");
let _ = child.kill(); let system = System::new_with_specifics(
RefreshKind::new().with_processes(ProcessRefreshKind::everything()),
);
let procs = system.processes_by_name("verge-mihomo");
for proc in procs {
log::debug!(target: "app", "kill all clash process");
proc.kill();
} }
Ok(()) Ok(())
} }
@@ -251,7 +255,7 @@ impl CoreManager {
/// 切换核心 /// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> { pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?; let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
const CLASH_CORES: [&str; 2] = ["clash-meta", "clash-meta-alpha"]; const CLASH_CORES: [&str; 2] = ["verge-mihomo", "verge-mihomo-alpha"];
if !CLASH_CORES.contains(&clash_core.as_str()) { if !CLASH_CORES.contains(&clash_core.as_str()) {
bail!("invalid clash core name \"{clash_core}\""); bail!("invalid clash core name \"{clash_core}\"");
@@ -262,7 +266,7 @@ impl CoreManager {
Config::verge().draft().clash_core = Some(clash_core); Config::verge().draft().clash_core = Some(clash_core);
// 更新订阅 // 更新订阅
Config::generate()?; Config::generate().await?;
self.check_config()?; self.check_config()?;
@@ -288,9 +292,8 @@ impl CoreManager {
/// 如果涉及端口和外部控制则需要重启 /// 如果涉及端口和外部控制则需要重启
pub async fn update_config(&self) -> Result<()> { pub async fn update_config(&self) -> Result<()> {
log::debug!(target: "app", "try to update clash config"); log::debug!(target: "app", "try to update clash config");
// 更新订阅 // 更新订阅
Config::generate()?; Config::generate().await?;
// 检查订阅是否正常 // 检查订阅是否正常
self.check_config()?; self.check_config()?;
@@ -300,20 +303,19 @@ impl CoreManager {
let path = dirs::path_to_str(&path)?; let path = dirs::path_to_str(&path)?;
// 发送请求 发送5次 // 发送请求 发送5次
for i in 0..5 { for i in 0..10 {
match clash_api::put_configs(path).await { match clash_api::put_configs(path).await {
Ok(_) => break, Ok(_) => break,
Err(err) => { Err(err) => {
if i < 4 { if i < 9 {
log::info!(target: "app", "{err}"); log::info!(target: "app", "{err}");
} else { } else {
bail!(err); bail!(err);
} }
} }
} }
sleep(Duration::from_millis(250)).await; sleep(Duration::from_millis(100)).await;
} }
Ok(()) Ok(())
} }
} }
+2 -2
View File
@@ -43,7 +43,7 @@ impl Hotkey {
} }
} }
} }
*self.current.lock() = hotkeys.clone(); self.current.lock().clone_from(hotkeys);
} }
Ok(()) Ok(())
@@ -142,7 +142,7 @@ impl Hotkey {
}); });
new_map.iter().for_each(|(&key, &func)| { new_map.iter().for_each(|(&key, &func)| {
if old_map.get(key).is_none() { if !old_map.contains_key(key) {
add_list.push((key, func)); add_list.push((key, func));
} }
}); });
-47
View File
@@ -1,47 +0,0 @@
/// 给clash内核的tun模式授权
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn grant_permission(core: String) -> anyhow::Result<()> {
use std::process::Command;
use tauri::utils::platform::current_exe;
let path = current_exe()?.with_file_name(core).canonicalize()?;
let path = path.display().to_string();
log::debug!("grant_permission path: {path}");
#[cfg(target_os = "macos")]
let output = {
let path = path.replace(' ', "\\\\ ");
let shell = format!("chown root:admin {path}\nchmod +sx {path}");
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
Command::new("osascript")
.args(vec!["-e", &command])
.output()?
};
#[cfg(target_os = "linux")]
let output = {
let path = path.replace(' ', "\\ "); // 避免路径中有空格
let shell = format!("setcap cap_net_bind_service,cap_net_admin,cap_dac_override=+ep {path}");
let sudo = match Command::new("which").arg("pkexec").output() {
Ok(output) => {
if output.stdout.is_empty() {
"sudo"
} else {
"pkexec"
}
}
Err(_) => "sudo",
};
Command::new(sudo).arg("sh").arg("-c").arg(shell).output()?
};
if output.status.success() {
Ok(())
} else {
let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
anyhow::bail!("{stderr}");
}
}
+2 -2
View File
@@ -1,13 +1,13 @@
pub mod clash_api; pub mod clash_api;
#[allow(clippy::module_inception)]
mod core; mod core;
pub mod handle; pub mod handle;
pub mod hotkey; pub mod hotkey;
pub mod logger; pub mod logger;
pub mod manager; pub mod service;
pub mod sysopt; pub mod sysopt;
pub mod timer; pub mod timer;
pub mod tray; pub mod tray;
pub mod service;
pub mod win_uwp; pub mod win_uwp;
pub use self::core::*; pub use self::core::*;
+63 -2
View File
@@ -1,4 +1,5 @@
use crate::config::Config; use crate::config::{Config, IVerge};
use crate::core::handle;
use crate::utils::dirs; use crate::utils::dirs;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -102,6 +103,12 @@ pub async fn install_service() -> Result<()> {
if !installer_path.exists() { if !installer_path.exists() {
bail!("installer not found"); bail!("installer not found");
} }
let _ = StdCommand::new("chmod")
.arg("+x")
.arg(installer_path.to_string_lossy().replace(" ", "\\ "))
.output();
let shell = installer_path.to_string_lossy().replace(" ", "\\\\ "); let shell = installer_path.to_string_lossy().replace(" ", "\\\\ ");
let command = format!(r#"do shell script "{shell}" with administrator privileges"#); let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
@@ -237,7 +244,21 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
} }
let clash_core = { Config::verge().latest().clash_core.clone() }; let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into()); let mut clash_core = clash_core.unwrap_or("verge-mihomo".into());
// compatibility
if clash_core.contains("clash") {
clash_core = "verge-mihomo".to_string();
Config::verge().draft().patch_config(IVerge {
clash_core: Some("verge-mihomo".to_string()),
..IVerge::default()
});
Config::verge().apply();
match Config::verge().data().save_file() {
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
}
let bin_ext = if cfg!(windows) { ".exe" } else { "" }; let bin_ext = if cfg!(windows) { ".exe" } else { "" };
let clash_bin = format!("{clash_core}{bin_ext}"); let clash_bin = format!("{clash_core}{bin_ext}");
@@ -297,3 +318,43 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
Ok(()) Ok(())
} }
/// set dns by service
pub async fn set_dns_by_service() -> Result<()> {
let url = format!("{SERVICE_URL}/set_dns");
let res = reqwest::ClientBuilder::new()
.no_proxy()
.build()?
.post(url)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
}
Ok(())
}
/// unset dns by service
pub async fn unset_dns_by_service() -> Result<()> {
let url = format!("{SERVICE_URL}/unset_dns");
let res = reqwest::ClientBuilder::new()
.no_proxy()
.build()?
.post(url)
.send()
.await?
.json::<JsonResponse>()
.await
.context("failed to connect to the Clash Verge Service")?;
if res.code != 0 {
bail!(res.msg);
}
Ok(())
}
+162 -39
View File
@@ -1,11 +1,14 @@
use crate::{config::Config, log_err}; use crate::{
config::{Config, IVerge},
log_err,
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::env::current_exe; use std::env::current_exe;
use std::sync::Arc; use std::sync::Arc;
use sysproxy::Sysproxy; use sysproxy::{Autoproxy, Sysproxy};
use tauri::async_runtime::Mutex as TokioMutex; use tauri::async_runtime::Mutex as TokioMutex;
pub struct Sysopt { pub struct Sysopt {
@@ -16,6 +19,13 @@ pub struct Sysopt {
/// recover it when exit /// recover it when exit
old_sysproxy: Arc<Mutex<Option<Sysproxy>>>, old_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
/// current auto proxy setting
cur_autoproxy: Arc<Mutex<Option<Autoproxy>>>,
/// record the original auto proxy
/// recover it when exit
old_autoproxy: Arc<Mutex<Option<Autoproxy>>>,
/// helps to auto launch the app /// helps to auto launch the app
auto_launch: Arc<Mutex<Option<AutoLaunch>>>, auto_launch: Arc<Mutex<Option<AutoLaunch>>>,
@@ -24,13 +34,49 @@ pub struct Sysopt {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;<local>"; static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1"; static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
static DEFAULT_BYPASS: &str = static DEFAULT_BYPASS: &str =
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>"; "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
fn get_bypass() -> String {
// let bypass = DEFAULT_BYPASS.to_string();
let use_default = Config::verge().latest().use_default_bypass.unwrap_or(true);
let res = {
let verge = Config::verge();
let verge = verge.latest();
verge.system_proxy_bypass.clone()
};
let custom_bypass = match res {
Some(bypass) => bypass,
None => "".to_string(),
};
#[cfg(target_os = "windows")]
let bypass = if custom_bypass.is_empty() {
DEFAULT_BYPASS.to_string()
} else {
if use_default {
format!("{};{}", DEFAULT_BYPASS, custom_bypass)
} else {
custom_bypass
}
};
#[cfg(not(target_os = "windows"))]
let bypass = if custom_bypass.is_empty() {
DEFAULT_BYPASS.to_string()
} else {
if use_default {
format!("{},{}", DEFAULT_BYPASS, custom_bypass)
} else {
custom_bypass
}
};
bypass
}
impl Sysopt { impl Sysopt {
pub fn global() -> &'static Sysopt { pub fn global() -> &'static Sysopt {
static SYSOPT: OnceCell<Sysopt> = OnceCell::new(); static SYSOPT: OnceCell<Sysopt> = OnceCell::new();
@@ -38,6 +84,8 @@ impl Sysopt {
SYSOPT.get_or_init(|| Sysopt { SYSOPT.get_or_init(|| Sysopt {
cur_sysproxy: Arc::new(Mutex::new(None)), cur_sysproxy: Arc::new(Mutex::new(None)),
old_sysproxy: Arc::new(Mutex::new(None)), old_sysproxy: Arc::new(Mutex::new(None)),
cur_autoproxy: Arc::new(Mutex::new(None)),
old_autoproxy: Arc::new(Mutex::new(None)),
auto_launch: Arc::new(Mutex::new(None)), auto_launch: Arc::new(Mutex::new(None)),
guard_state: Arc::new(TokioMutex::new(false)), guard_state: Arc::new(TokioMutex::new(false)),
}) })
@@ -49,29 +97,48 @@ impl Sysopt {
.latest() .latest()
.verge_mixed_port .verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port()); .unwrap_or(Config::clash().data().get_mixed_port());
let pac_port = IVerge::get_singleton_port();
let (enable, bypass) = { let (enable, pac) = {
let verge = Config::verge(); let verge = Config::verge();
let verge = verge.latest(); let verge = verge.latest();
( (
verge.enable_system_proxy.unwrap_or(false), verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(), verge.proxy_auto_config.unwrap_or(false),
) )
}; };
let mut sys = Sysproxy {
let current = Sysproxy {
enable, enable,
host: String::from("127.0.0.1"), host: String::from("127.0.0.1"),
port, port,
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), bypass: get_bypass(),
}; };
let mut auto = Autoproxy {
if enable { enable,
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
if pac {
sys.enable = false;
let old = Sysproxy::get_system_proxy().ok(); let old = Sysproxy::get_system_proxy().ok();
current.set_system_proxy()?; sys.set_system_proxy()?;
*self.old_sysproxy.lock() = old; *self.old_sysproxy.lock() = old;
*self.cur_sysproxy.lock() = Some(current); *self.cur_sysproxy.lock() = Some(sys);
let old = Autoproxy::get_auto_proxy().ok();
auto.set_auto_proxy()?;
*self.old_autoproxy.lock() = old;
*self.cur_autoproxy.lock() = Some(auto);
} else {
auto.enable = false;
let old = Autoproxy::get_auto_proxy().ok();
auto.set_auto_proxy()?;
*self.old_autoproxy.lock() = old;
*self.cur_autoproxy.lock() = Some(auto);
let old = Sysproxy::get_system_proxy().ok();
sys.set_system_proxy()?;
*self.old_sysproxy.lock() = old;
*self.cur_sysproxy.lock() = Some(sys);
} }
// run the system proxy guard // run the system proxy guard
@@ -83,34 +150,57 @@ impl Sysopt {
pub fn update_sysproxy(&self) -> Result<()> { pub fn update_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock(); let mut cur_sysproxy = self.cur_sysproxy.lock();
let old_sysproxy = self.old_sysproxy.lock(); let old_sysproxy = self.old_sysproxy.lock();
let mut cur_autoproxy = self.cur_autoproxy.lock();
let old_autoproxy = self.old_autoproxy.lock();
if cur_sysproxy.is_none() || old_sysproxy.is_none() { let (enable, pac) = {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.unwrap_or(false),
verge.proxy_auto_config.unwrap_or(false),
)
};
if pac && (cur_autoproxy.is_none() || old_autoproxy.is_none()) {
drop(cur_autoproxy);
drop(old_autoproxy);
return self.init_sysproxy();
}
if !pac && (cur_sysproxy.is_none() || old_sysproxy.is_none()) {
drop(cur_sysproxy); drop(cur_sysproxy);
drop(old_sysproxy); drop(old_sysproxy);
return self.init_sysproxy(); return self.init_sysproxy();
} }
let (enable, bypass) = {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(),
)
};
let mut sysproxy = cur_sysproxy.take().unwrap();
sysproxy.enable = enable;
sysproxy.bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
let port = Config::verge() let port = Config::verge()
.latest() .latest()
.verge_mixed_port .verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port()); .unwrap_or(Config::clash().data().get_mixed_port());
let pac_port = IVerge::get_singleton_port();
let mut sysproxy = cur_sysproxy.take().unwrap();
sysproxy.bypass = get_bypass();
sysproxy.port = port; sysproxy.port = port;
sysproxy.set_system_proxy()?; let mut autoproxy = cur_autoproxy.take().unwrap();
*cur_sysproxy = Some(sysproxy); autoproxy.url = format!("http://127.0.0.1:{pac_port}/commands/pac");
if pac {
sysproxy.enable = false;
sysproxy.set_system_proxy()?;
*cur_sysproxy = Some(sysproxy);
autoproxy.enable = enable;
autoproxy.set_auto_proxy()?;
*cur_autoproxy = Some(autoproxy);
} else {
autoproxy.enable = false;
autoproxy.set_auto_proxy()?;
*cur_autoproxy = Some(autoproxy);
sysproxy.enable = enable;
sysproxy.set_system_proxy()?;
*cur_sysproxy = Some(sysproxy);
}
Ok(()) Ok(())
} }
@@ -119,8 +209,11 @@ impl Sysopt {
pub fn reset_sysproxy(&self) -> Result<()> { pub fn reset_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock(); let mut cur_sysproxy = self.cur_sysproxy.lock();
let mut old_sysproxy = self.old_sysproxy.lock(); let mut old_sysproxy = self.old_sysproxy.lock();
let mut cur_autoproxy = self.cur_autoproxy.lock();
let mut old_autoproxy = self.old_autoproxy.lock();
let cur_sysproxy = cur_sysproxy.take(); let cur_sysproxy = cur_sysproxy.take();
let cur_autoproxy = cur_autoproxy.take();
if let Some(mut old) = old_sysproxy.take() { if let Some(mut old) = old_sysproxy.take() {
// 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置 // 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置
@@ -144,6 +237,28 @@ impl Sysopt {
log::info!(target: "app", "reset proxy with no action"); log::info!(target: "app", "reset proxy with no action");
} }
if let Some(mut old) = old_autoproxy.take() {
// 如果原代理和当前代理 URL一致,就disable关闭,否则就恢复原代理设置
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
let url_same = cur_autoproxy.map_or(true, |cur| old.url == cur.url);
if old.enable && url_same {
old.enable = false;
log::info!(target: "app", "reset proxy by disabling the original proxy");
} else {
log::info!(target: "app", "reset proxy to the original proxy");
}
old.set_auto_proxy()?;
} else if let Some(mut cur @ Autoproxy { enable: true, .. }) = cur_autoproxy {
// 没有原代理,就按现在的代理设置disable即可
log::info!(target: "app", "reset proxy by disabling the current proxy");
cur.enable = false;
cur.set_auto_proxy()?;
} else {
log::info!(target: "app", "reset proxy with no action");
}
Ok(()) Ok(())
} }
@@ -249,14 +364,14 @@ impl Sysopt {
loop { loop {
sleep(Duration::from_secs(wait_secs)).await; sleep(Duration::from_secs(wait_secs)).await;
let (enable, guard, guard_duration, bypass) = { let (enable, guard, guard_duration, pac) = {
let verge = Config::verge(); let verge = Config::verge();
let verge = verge.latest(); let verge = verge.latest();
( (
verge.enable_system_proxy.unwrap_or(false), verge.enable_system_proxy.unwrap_or(false),
verge.enable_proxy_guard.unwrap_or(false), verge.enable_proxy_guard.unwrap_or(false),
verge.proxy_guard_duration.unwrap_or(10), verge.proxy_guard_duration.unwrap_or(10),
verge.system_proxy_bypass.clone(), verge.proxy_auto_config.unwrap_or(false),
) )
}; };
@@ -276,15 +391,23 @@ impl Sysopt {
.verge_mixed_port .verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port()) .unwrap_or(Config::clash().data().get_mixed_port())
}; };
let pac_port = IVerge::get_singleton_port();
if pac {
let autoproxy = Autoproxy {
enable: true,
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
log_err!(autoproxy.set_auto_proxy());
} else {
let sysproxy = Sysproxy {
enable: true,
host: "127.0.0.1".into(),
port,
bypass: get_bypass(),
};
let sysproxy = Sysproxy { log_err!(sysproxy.set_system_proxy());
enable: true, }
host: "127.0.0.1".into(),
port,
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
};
log_err!(sysproxy.set_system_proxy());
} }
let mut state = guard_state.lock().await; let mut state = guard_state.lock().await;
+35 -11
View File
@@ -88,7 +88,7 @@ impl Tray {
), ),
)) ))
.add_native_item(SystemTrayMenuItem::Separator) .add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit", t!("Quit", "退出")).accelerator("CmdOrControl+Q")) .add_item(CustomMenuItem::new("quit", t!("Quit", "退出")))
} }
pub fn update_systray(app_handle: &AppHandle) -> Result<()> { pub fn update_systray(app_handle: &AppHandle) -> Result<()> {
@@ -172,15 +172,31 @@ impl Tray {
let verge = verge.latest(); let verge = verge.latest();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
#[cfg(target_os = "macos")]
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
let common_tray_icon = verge.common_tray_icon.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 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 tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
#[cfg(target_os = "macos")]
match tray_icon.as_str() {
"monochrome" => {
let _ = tray.set_icon_as_template(true);
}
"colorful" => {
let _ = tray.set_icon_as_template(false);
}
_ => {}
}
let mut indication_icon = if *system_proxy { let mut indication_icon = if *system_proxy {
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../icons/tray-icon-sys.png").to_vec();
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let mut icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec(); let mut icon = match tray_icon.as_str() {
"monochrome" => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
"colorful" => include_bytes!("../../icons/tray-icon-sys.ico").to_vec(),
_ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../icons/tray-icon-sys.ico").to_vec();
if *sysproxy_tray_icon { if *sysproxy_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons"); let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("sysproxy.png"); let png_path = icon_dir_path.join("sysproxy.png");
@@ -193,10 +209,14 @@ impl Tray {
} }
icon icon
} else { } else {
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../icons/tray-icon.png").to_vec();
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let mut icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec(); let mut icon = match tray_icon.as_str() {
"monochrome" => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
"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 { if *common_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons"); let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("common.png"); let png_path = icon_dir_path.join("common.png");
@@ -211,10 +231,14 @@ impl Tray {
}; };
if *tun_mode { if *tun_mode {
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let mut icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec(); let mut icon = match tray_icon.as_str() {
"monochrome" => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
"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 { if *tun_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons"); let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("tun.png"); let png_path = icon_dir_path.join("tun.png");
+1 -1
View File
@@ -1,4 +1,4 @@
function main(config) { function main(config, _name) {
if (config.mode === "script") { if (config.mode === "script") {
config.mode = "rule"; config.mode = "rule";
} }
@@ -1,4 +1,4 @@
function main(config) { function main(config, _name) {
if (Array.isArray(config.proxies)) { if (Array.isArray(config.proxies)) {
config.proxies.forEach((p, i) => { config.proxies.forEach((p, i) => {
if (p.type === "hysteria" && typeof p.alpn === "string") { if (p.type === "hysteria" && typeof p.alpn === "string") {
+24 -8
View File
@@ -1,3 +1,4 @@
use super::SeqMap;
use crate::{ use crate::{
config::PrfItem, config::PrfItem,
utils::{dirs, help}, utils::{dirs, help},
@@ -15,6 +16,9 @@ pub struct ChainItem {
pub enum ChainType { pub enum ChainType {
Merge(Mapping), Merge(Mapping),
Script(String), Script(String),
Rules(SeqMap),
Proxies(SeqMap),
Groups(SeqMap),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -43,7 +47,19 @@ impl From<&PrfItem> for Option<ChainItem> {
}), }),
"merge" => Some(ChainItem { "merge" => Some(ChainItem {
uid, uid,
data: ChainType::Merge(help::read_merge_mapping(&path).ok()?), data: ChainType::Merge(help::read_mapping(&path).ok()?),
}),
"rules" => Some(ChainItem {
uid,
data: ChainType::Rules(help::read_seq_map(&path).ok()?),
}),
"proxies" => Some(ChainItem {
uid,
data: ChainType::Proxies(help::read_seq_map(&path).ok()?),
}),
"groups" => Some(ChainItem {
uid,
data: ChainType::Groups(help::read_seq_map(&path).ok()?),
}), }),
_ => None, _ => None,
} }
@@ -88,13 +104,13 @@ impl ChainItem {
impl ChainSupport { impl ChainSupport {
pub fn is_support(&self, core: Option<&String>) -> bool { pub fn is_support(&self, core: Option<&String>) -> bool {
match core { match core {
Some(core) => match (self, core.as_str()) { Some(core) => matches!(
(ChainSupport::All, _) => true, (self, core.as_str()),
(ChainSupport::Clash, "clash") => true, (ChainSupport::All, _)
(ChainSupport::ClashMeta, "clash-meta") => true, | (ChainSupport::Clash, "clash")
(ChainSupport::ClashMetaAlpha, "clash-meta-alpha") => true, | (ChainSupport::ClashMeta, "verge-mihomo")
_ => false, | (ChainSupport::ClashMetaAlpha, "verge-mihomo-alpha")
}, ),
None => true, None => true,
} }
} }
+2 -19
View File
@@ -11,8 +11,8 @@ pub const HANDLE_FIELDS: [&str; 11] = [
"allow-lan", "allow-lan",
"log-level", "log-level",
"ipv6", "ipv6",
"secret",
"external-controller", "external-controller",
"secret",
]; ];
pub const DEFAULT_FIELDS: [&str; 5] = [ pub const DEFAULT_FIELDS: [&str; 5] = [
@@ -23,19 +23,6 @@ pub const DEFAULT_FIELDS: [&str; 5] = [
"rules", "rules",
]; ];
pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
let mut ret = Mapping::new();
for (key, value) in config.into_iter() {
if let Some(key) = key.as_str() {
if filter.contains(&key.to_string()) {
ret.insert(Value::from(key), value);
}
}
}
ret
}
pub fn use_lowercase(config: Mapping) -> Mapping { pub fn use_lowercase(config: Mapping) -> Mapping {
let mut ret = Mapping::new(); let mut ret = Mapping::new();
@@ -60,11 +47,7 @@ pub fn use_sort(config: Mapping) -> Mapping {
let supported_keys: HashSet<&str> = HANDLE_FIELDS.into_iter().chain(DEFAULT_FIELDS).collect(); let supported_keys: HashSet<&str> = HANDLE_FIELDS.into_iter().chain(DEFAULT_FIELDS).collect();
let config_keys: HashSet<&str> = config let config_keys: HashSet<&str> = config.keys().filter_map(|e| e.as_str()).collect();
.keys()
.filter_map(|e| e.as_str())
.into_iter()
.collect();
config_keys.difference(&supported_keys).for_each(|&key| { config_keys.difference(&supported_keys).for_each(|&key| {
let key = Value::from(key); let key = Value::from(key);
+16 -86
View File
@@ -1,95 +1,25 @@
use super::{use_filter, use_lowercase}; use super::use_lowercase;
use serde_yaml::{self, Mapping, Sequence, Value}; use serde_yaml::{self, Mapping, Value};
const MERGE_FIELDS: [&str; 10] = [ fn deep_merge(a: &mut Value, b: &Value) {
"prepend-rules", match (a, b) {
"append-rules", (&mut Value::Mapping(ref mut a), Value::Mapping(b)) => {
"prepend-rule-providers", for (k, v) in b {
"append-rule-providers", deep_merge(a.entry(k.clone()).or_insert(Value::Null), v);
"prepend-proxies",
"append-proxies",
"prepend-proxy-providers",
"append-proxy-providers",
"prepend-proxy-groups",
"append-proxy-groups",
];
pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
// 直接覆盖原字段
use_lowercase(merge.clone())
.into_iter()
.filter(|(key, _)| !MERGE_FIELDS.contains(&key.as_str().unwrap_or_default()))
.for_each(|(key, value)| {
config.insert(key, value);
});
let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
let merge = use_filter(merge, &merge_list.collect());
["rule-providers", "proxy-providers"]
.iter()
.for_each(|key_str| {
let key_val = Value::from(key_str.to_string());
let mut map = Mapping::default();
map = config.get(&key_val).map_or(map.clone(), |val| {
val.as_mapping().map_or(map, |v| v.clone())
});
let pre_key = Value::from(format!("prepend-{key_str}"));
let post_key = Value::from(format!("append-{key_str}"));
if let Some(pre_val) = merge.get(&pre_key) {
if pre_val.is_mapping() {
let mut pre_val = pre_val.as_mapping().unwrap().clone();
pre_val.extend(map);
map = pre_val;
}
} }
}
(a, b) => *a = b.clone(),
}
}
if let Some(post_val) = merge.get(&post_key) { pub fn use_merge(merge: Mapping, config: Mapping) -> Mapping {
if post_val.is_mapping() { let mut config = Value::from(config);
map.extend(post_val.as_mapping().unwrap().clone()); let merge = use_lowercase(merge.clone());
}
}
if !map.is_empty() { deep_merge(&mut config, &Value::from(merge));
config.insert(key_val, Value::from(map));
}
});
["rules", "proxies", "proxy-groups"] let config = config.as_mapping().unwrap().clone();
.iter()
.for_each(|key_str| {
let key_val = Value::from(key_str.to_string());
let mut list = Sequence::default();
list = config.get(&key_val).map_or(list.clone(), |val| {
val.as_sequence().map_or(list, |v| v.clone())
});
let pre_key = Value::from(format!("prepend-{key_str}"));
let post_key = Value::from(format!("append-{key_str}"));
if let Some(pre_val) = merge.get(&pre_key) {
if pre_val.is_sequence() {
let mut pre_val = pre_val.as_sequence().unwrap().clone();
pre_val.extend(list);
list = pre_val;
}
}
if let Some(post_val) = merge.get(&post_key) {
if post_val.is_sequence() {
list.extend(post_val.as_sequence().unwrap().clone());
}
}
if !list.is_empty() {
config.insert(key_val, Value::from(list));
}
});
config config
} }
+181 -37
View File
@@ -2,14 +2,17 @@ mod chain;
pub mod field; pub mod field;
mod merge; mod merge;
mod script; mod script;
pub mod seq;
mod tun; mod tun;
use self::chain::*; use self::chain::*;
use self::field::*; use self::field::*;
use self::merge::*; use self::merge::*;
use self::script::*; use self::script::*;
use self::seq::*;
use self::tun::*; use self::tun::*;
use crate::config::Config; use crate::config::Config;
use crate::utils::tmpl;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
@@ -18,63 +21,184 @@ type ResultLog = Vec<(String, String)>;
/// Enhance mode /// Enhance mode
/// 返回最终订阅、该订阅包含的键、和script执行的结果 /// 返回最终订阅、该订阅包含的键、和script执行的结果
pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) { pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// config.yaml 的订阅 // config.yaml 的订阅
let clash_config = { Config::clash().latest().0.clone() }; let clash_config = { Config::clash().latest().0.clone() };
let (clash_core, enable_tun, enable_builtin) = { let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled) = {
let verge = Config::verge(); let verge = Config::verge();
let verge = verge.latest(); let verge = verge.latest();
( (
verge.clash_core.clone(), verge.clash_core.clone(),
verge.enable_tun_mode.unwrap_or(false), verge.enable_tun_mode.unwrap_or(false),
verge.enable_builtin_enhanced.unwrap_or(true), verge.enable_builtin_enhanced.unwrap_or(true),
verge.verge_socks_enabled.unwrap_or(false),
verge.verge_http_enabled.unwrap_or(false),
) )
}; };
#[cfg(not(target_os = "windows"))]
let redir_enabled = {
let verge = Config::verge();
let verge = verge.latest();
verge.verge_redir_enabled.unwrap_or(false)
};
#[cfg(target_os = "linux")]
let tproxy_enabled = {
let verge = Config::verge();
let verge = verge.latest();
verge.verge_tproxy_enabled.unwrap_or(false)
};
// 从profiles里拿东西 // 从profiles里拿东西
let (mut config, chain) = { let (
mut config,
merge_item,
script_item,
rules_item,
proxies_item,
groups_item,
global_merge,
global_script,
profile_name,
) = {
let profiles = Config::profiles(); let profiles = Config::profiles();
let profiles = profiles.latest(); let profiles = profiles.latest();
let current = profiles.current_mapping().unwrap_or_default(); let current = profiles.current_mapping().unwrap_or_default();
let merge = profiles
.get_item(&profiles.current_merge().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Merge(Mapping::new()),
});
let script = profiles
.get_item(&profiles.current_script().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
let rules = profiles
.get_item(&profiles.current_rules().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Rules(SeqMap::default()),
});
let proxies = profiles
.get_item(&profiles.current_proxies().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Proxies(SeqMap::default()),
});
let groups = profiles
.get_item(&profiles.current_groups().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Groups(SeqMap::default()),
});
let chain = match profiles.chain.as_ref() { let global_merge = profiles
Some(chain) => chain .get_item(&"Merge".to_string())
.iter() .ok()
.filter_map(|uid| profiles.get_item(uid).ok()) .and_then(<Option<ChainItem>>::from)
.filter_map(<Option<ChainItem>>::from) .unwrap_or_else(|| ChainItem {
.collect::<Vec<ChainItem>>(), uid: "Merge".into(),
None => vec![], data: ChainType::Merge(Mapping::new()),
}; });
(current, chain) let global_script = profiles
.get_item(&"Script".to_string())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "Script".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
let name = profiles
.get_item(&profiles.get_current().unwrap_or_default())
.ok()
.and_then(|item| item.name.clone())
.unwrap_or_default();
(
current,
merge,
script,
rules,
proxies,
groups,
global_merge,
global_script,
name,
)
}; };
let mut result_map = HashMap::new(); // 保存脚本日志 let mut result_map = HashMap::new(); // 保存脚本日志
let mut exists_keys = use_keys(&config); // 保存出现过的keys let mut exists_keys = use_keys(&config); // 保存出现过的keys
// 处理用户的profile // 全局Merge和Script
chain.into_iter().for_each(|item| match item.data { if let ChainType::Merge(merge) = global_merge.data {
ChainType::Merge(merge) => { exists_keys.extend(use_keys(&merge));
exists_keys.extend(use_keys(&merge)); config = use_merge(merge, config.to_owned());
config = use_merge(merge, config.to_owned()); }
}
ChainType::Script(script) => {
let mut logs = vec![];
match use_script(script, config.to_owned()) { if let ChainType::Script(script) = global_script.data {
Ok((res_config, res_logs)) => { let mut logs = vec![];
exists_keys.extend(use_keys(&res_config));
config = res_config; match use_script(script, config.to_owned(), profile_name.to_owned()) {
logs.extend(res_logs); Ok((res_config, res_logs)) => {
} exists_keys.extend(use_keys(&res_config));
Err(err) => logs.push(("exception".into(), err.to_string())), config = res_config;
logs.extend(res_logs);
} }
Err(err) => logs.push(("exception".into(), err.to_string())),
result_map.insert(item.uid, logs);
} }
});
result_map.insert(global_script.uid, logs);
}
// 订阅关联的Merge、Script、Rules、Proxies、Groups
if let ChainType::Rules(rules) = rules_item.data {
config = use_seq(rules, config.to_owned(), "rules");
}
if let ChainType::Proxies(proxies) = proxies_item.data {
config = use_seq(proxies, config.to_owned(), "proxies");
}
if let ChainType::Groups(groups) = groups_item.data {
config = use_seq(groups, config.to_owned(), "proxy-groups");
}
if let ChainType::Merge(merge) = merge_item.data {
exists_keys.extend(use_keys(&merge));
config = use_merge(merge, config.to_owned());
}
if let ChainType::Script(script) = script_item.data {
let mut logs = vec![];
match use_script(script, config.to_owned(), profile_name.to_owned()) {
Ok((res_config, res_logs)) => {
exists_keys.extend(use_keys(&res_config));
config = res_config;
logs.extend(res_logs);
}
Err(err) => logs.push(("exception".into(), err.to_string())),
}
result_map.insert(script_item.uid, logs);
}
// 合并默认的config // 合并默认的config
for (key, value) in clash_config.into_iter() { for (key, value) in clash_config.into_iter() {
@@ -88,6 +212,28 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
} }
config.insert("tun".into(), tun.into()); config.insert("tun".into(), tun.into());
} else { } else {
if key.as_str() == Some("socks-port") && !socks_enabled {
config.remove("socks-port");
continue;
}
if key.as_str() == Some("port") && !http_enabled {
config.remove("port");
continue;
}
#[cfg(not(target_os = "windows"))]
{
if key.as_str() == Some("redir-port") && !redir_enabled {
config.remove("redir-port");
continue;
}
}
#[cfg(target_os = "linux")]
{
if key.as_str() == Some("tproxy-port") && !tproxy_enabled {
config.remove("tproxy-port");
continue;
}
}
config.insert(key, value); config.insert(key, value);
} }
} }
@@ -100,26 +246,24 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
.map(|(_, c)| c) .map(|(_, c)| c)
.for_each(|item| { .for_each(|item| {
log::debug!(target: "app", "run builtin script {}", item.uid); log::debug!(target: "app", "run builtin script {}", item.uid);
if let ChainType::Script(script) = item.data {
match item.data { match use_script(script, config.to_owned(), "".to_string()) {
ChainType::Script(script) => match use_script(script, config.to_owned()) {
Ok((res_config, _)) => { Ok((res_config, _)) => {
config = res_config; config = res_config;
} }
Err(err) => { Err(err) => {
log::error!(target: "app", "builtin script error `{err}`"); log::error!(target: "app", "builtin script error `{err}`");
} }
}, }
_ => {}
} }
}); });
} }
config = use_tun(config, enable_tun); config = use_tun(config, enable_tun).await;
config = use_sort(config); config = use_sort(config);
let mut exists_set = HashSet::new(); let mut exists_set = HashSet::new();
exists_set.extend(exists_keys.into_iter()); exists_set.extend(exists_keys);
exists_keys = exists_set.into_iter().collect(); exists_keys = exists_set.into_iter().collect();
(config, exists_keys, result_map) (config, exists_keys, result_map)
+8 -4
View File
@@ -2,7 +2,11 @@ use super::use_lowercase;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use serde_yaml::Mapping; use serde_yaml::Mapping;
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> { pub fn use_script(
script: String,
config: Mapping,
name: String,
) -> Result<(Mapping, Vec<(String, String)>)> {
use boa_engine::{native_function::NativeFunction, Context, JsValue, Source}; use boa_engine::{native_function::NativeFunction, Context, JsValue, Source};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
let mut context = Context::default(); let mut context = Context::default();
@@ -16,7 +20,7 @@ pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(Stri
2, 2,
NativeFunction::from_closure( NativeFunction::from_closure(
move |_: &JsValue, args: &[JsValue], context: &mut Context| { move |_: &JsValue, args: &[JsValue], context: &mut Context| {
let level = args.get(0).unwrap().to_string(context)?; let level = args.first().unwrap().to_string(context)?;
let level = level.to_std_string().unwrap(); let level = level.to_std_string().unwrap();
let data = args.get(1).unwrap().to_string(context)?; let data = args.get(1).unwrap().to_string(context)?;
let data = data.to_std_string().unwrap(); let data = data.to_std_string().unwrap();
@@ -42,7 +46,7 @@ pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(Stri
let code = format!( let code = format!(
r#"try{{ r#"try{{
{script}; {script};
JSON.stringify(main({config_str})||'') JSON.stringify(main({config_str},'{name}')||'')
}} catch(err) {{ }} catch(err) {{
`__error_flag__ ${{err.toString()}}` `__error_flag__ ${{err.toString()}}`
}}"# }}"#
@@ -97,7 +101,7 @@ fn test_script() {
"#; "#;
let config = serde_yaml::from_str(config).unwrap(); let config = serde_yaml::from_str(config).unwrap();
let (config, results) = use_script(script.into(), config).unwrap(); let (config, results) = use_script(script.into(), config, "".to_string()).unwrap();
let config_str = serde_yaml::to_string(&config).unwrap(); let config_str = serde_yaml::to_string(&config).unwrap();
+55
View File
@@ -0,0 +1,55 @@
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Sequence, Value};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SeqMap {
prepend: Sequence,
append: Sequence,
delete: Sequence,
}
pub fn use_seq(seq_map: SeqMap, config: Mapping, name: &str) -> Mapping {
let mut prepend = seq_map.prepend;
let append = seq_map.append;
let delete = seq_map.delete;
let origin_seq = config.get(&name).map_or(Sequence::default(), |val| {
val.as_sequence().unwrap_or(&Sequence::default()).clone()
});
let mut seq = origin_seq.clone();
let mut delete_names = Vec::new();
for item in delete {
let item = item.clone();
if let Some(name) = if item.is_string() {
Some(item)
} else {
item.get("name").map(|y| y.clone())
} {
delete_names.push(name.clone());
}
}
seq.retain(|x| {
if let Some(x_name) = if x.is_string() {
Some(x)
} else {
x.get("name")
} {
!delete_names.contains(&x_name)
} else {
true
}
});
prepend.reverse();
for item in prepend {
seq.insert(0, item);
}
for item in append {
seq.push(item);
}
let mut config = config.clone();
config.insert(Value::from(name), Value::from(seq));
return config;
}
+4 -53
View File
@@ -1,3 +1,4 @@
use crate::{core::service, log_err};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
macro_rules! revise { macro_rules! revise {
@@ -17,7 +18,7 @@ macro_rules! append {
}; };
} }
pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping { pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
let tun_key = Value::from("tun"); let tun_key = Value::from("tun");
let tun_val = config.get(&tun_key); let tun_val = config.get(&tun_key);
@@ -34,60 +35,10 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
revise!(config, "tun", tun_val); revise!(config, "tun", tun_val);
if enable { if enable {
#[cfg(target_os = "macos")] log_err!(service::set_dns_by_service().await);
{
use crate::utils::dirs;
use tauri::api::process::Command;
log::info!(target: "app", "try to set system dns");
let resource_dir = dirs::app_resources_dir().unwrap();
let script = resource_dir.join("set_dns.sh");
let script = script.to_string_lossy();
match Command::new("bash")
.args([script])
.current_dir(resource_dir)
.status()
{
Ok(status) => {
if status.success() {
log::info!(target: "app", "set system dns successfully");
} else {
let code = status.code().unwrap_or(-1);
log::error!(target: "app", "set system dns failed: {code}");
}
}
Err(err) => {
log::error!(target: "app", "set system dns failed: {err}");
}
}
}
use_dns_for_tun(config) use_dns_for_tun(config)
} else { } else {
#[cfg(target_os = "macos")] log_err!(service::unset_dns_by_service().await);
{
use crate::utils::dirs;
use tauri::api::process::Command;
log::info!(target: "app", "try to unset system dns");
let resource_dir = dirs::app_resources_dir().unwrap();
let script = resource_dir.join("unset_dns.sh");
let script = script.to_string_lossy();
match Command::new("bash")
.args([script])
.current_dir(resource_dir)
.status()
{
Ok(status) => {
if status.success() {
log::info!(target: "app", "unset system dns successfully");
} else {
let code = status.code().unwrap_or(-1);
log::error!(target: "app", "unset system dns failed: {code}");
}
}
Err(err) => {
log::error!(target: "app", "unset system dns failed: {err}");
}
}
}
config config
} }
} }
+82 -57
View File
@@ -106,49 +106,14 @@ pub fn toggle_tun_mode() {
pub async fn patch_clash(patch: Mapping) -> Result<()> { pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::clash().draft().patch_config(patch.clone()); Config::clash().draft().patch_config(patch.clone());
match { let res = {
let redir_port = patch.get("redir-port");
let tproxy_port = patch.get("tproxy-port");
let mixed_port = patch.get("mixed-port");
let socks_port = patch.get("socks-port");
let port = patch.get("port");
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
if mixed_port.is_some() && !enable_random_port {
let changed = mixed_port.unwrap()
!= Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
// 检查端口占用
if changed {
if let Some(port) = mixed_port.unwrap().as_u64() {
if !port_scanner::local_port_available(port as u16) {
Config::clash().discard();
bail!("port already in use");
}
}
}
};
// 激活订阅 // 激活订阅
if redir_port.is_some() if patch.get("secret").is_some() || patch.get("external-controller").is_some() {
|| tproxy_port.is_some() Config::generate().await?;
|| mixed_port.is_some()
|| socks_port.is_some()
|| port.is_some()
|| patch.get("secret").is_some()
|| patch.get("external-controller").is_some()
{
Config::generate()?;
CoreManager::global().run_core().await?; CoreManager::global().run_core().await?;
handle::Handle::refresh_clash(); handle::Handle::refresh_clash();
} }
// 更新系统代理
if mixed_port.is_some() {
log_err!(sysopt::Sysopt::global().init_sysproxy());
}
if patch.get("mode").is_some() { if patch.get("mode").is_some() {
log_err!(handle::Handle::update_systray_part()); log_err!(handle::Handle::update_systray_part());
} }
@@ -156,7 +121,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::runtime().latest().patch_config(patch); Config::runtime().latest().patch_config(patch);
<Result<()>>::Ok(()) <Result<()>>::Ok(())
} { };
match res {
Ok(()) => { Ok(()) => {
Config::clash().apply(); Config::clash().apply();
Config::clash().data().save_config()?; Config::clash().data().save_config()?;
@@ -173,33 +139,80 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
/// 一般都是一个个的修改 /// 一般都是一个个的修改
pub async fn patch_verge(patch: IVerge) -> Result<()> { pub async fn patch_verge(patch: IVerge) -> Result<()> {
Config::verge().draft().patch_config(patch.clone()); Config::verge().draft().patch_config(patch.clone());
let tun_mode = patch.enable_tun_mode; let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch; let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy; 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 proxy_bypass = patch.system_proxy_bypass;
let language = patch.language; let language = patch.language;
let port = patch.verge_mixed_port; let mixed_port = patch.verge_mixed_port;
#[cfg(target_os = "macos")]
let tray_icon = patch.tray_icon;
let common_tray_icon = patch.common_tray_icon; let common_tray_icon = patch.common_tray_icon;
let sysproxy_tray_icon = patch.sysproxy_tray_icon; let sysproxy_tray_icon = patch.sysproxy_tray_icon;
let tun_tray_icon = patch.tun_tray_icon; let tun_tray_icon = patch.tun_tray_icon;
#[cfg(not(target_os = "windows"))]
match { 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 res = {
let service_mode = patch.enable_service_mode; let service_mode = patch.enable_service_mode;
let mut generated = false;
if service_mode.is_some() { if service_mode.is_some() {
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap()); log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
if !generated {
Config::generate()?; Config::generate().await?;
CoreManager::global().run_core().await?; CoreManager::global().run_core().await?;
generated = true;
}
} else if tun_mode.is_some() { } else if tun_mode.is_some() {
update_core_config().await?; update_core_config().await?;
} }
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
if !generated {
Config::generate().await?;
CoreManager::global().run_core().await?;
generated = true;
}
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
if !generated {
Config::generate().await?;
CoreManager::global().run_core().await?;
generated = true;
}
}
if socks_enabled.is_some()
|| http_enabled.is_some()
|| socks_port.is_some()
|| http_port.is_some()
|| mixed_port.is_some()
{
if !generated {
Config::generate().await?;
CoreManager::global().run_core().await?;
}
}
if auto_launch.is_some() { if auto_launch.is_some() {
sysopt::Sysopt::global().update_launch()?; sysopt::Sysopt::global().update_launch()?;
} }
if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() { if system_proxy.is_some()
|| proxy_bypass.is_some()
|| mixed_port.is_some()
|| pac.is_some()
|| pac_content.is_some()
{
sysopt::Sysopt::global().update_sysproxy()?; sysopt::Sysopt::global().update_sysproxy()?;
sysopt::Sysopt::global().guard_proxy(); sysopt::Sysopt::global().guard_proxy();
} }
@@ -222,9 +235,14 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
{ {
handle::Handle::update_systray_part()?; handle::Handle::update_systray_part()?;
} }
#[cfg(target_os = "macos")]
if tray_icon.is_some() {
handle::Handle::update_systray_part()?;
}
<Result<()>>::Ok(()) <Result<()>>::Ok(())
} { };
match res {
Ok(()) => { Ok(()) => {
Config::verge().apply(); Config::verge().apply();
Config::verge().data().save_file()?; Config::verge().data().save_file()?;
@@ -259,7 +277,6 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
Some((url, opt)) => { Some((url, opt)) => {
let merged_opt = PrfOption::merge(opt, option); let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?; let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
let profiles = Config::profiles(); let profiles = Config::profiles();
let mut profiles = profiles.latest(); let mut profiles = profiles.latest();
profiles.update_item(uid.clone(), item)?; profiles.update_item(uid.clone(), item)?;
@@ -354,11 +371,19 @@ pub async fn test_delay(url: String) -> Result<u32> {
.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"); .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 start = Instant::now();
let response = request.send().await?; let response = request.send().await;
if response.status().is_success() { match response {
let delay = start.elapsed().as_millis() as u32; Ok(response) => {
Ok(delay) log::trace!(target: "app", "test_delay response: {:#?}", response);
} else { if response.status().is_success() {
Ok(10000u32) Ok(start.elapsed().as_millis() as u32)
} else {
Ok(10000u32)
}
}
Err(err) => {
log::trace!(target: "app", "test_delay error: {:#?}", err);
Err(err.into())
}
} }
} }
+16 -4
View File
@@ -15,8 +15,15 @@ use tauri::{api, SystemTray};
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
// 单例检测 // 单例检测
if server::check_singleton().is_err() { let app_exists: bool = tauri::async_runtime::block_on(async move {
println!("app exists"); if server::check_singleton().await.is_err() {
println!("app exists");
true
} else {
false
}
});
if app_exists {
return Ok(()); return Ok(());
} }
@@ -29,21 +36,24 @@ fn main() -> std::io::Result<()> {
let mut builder = tauri::Builder::default() let mut builder = tauri::Builder::default()
.system_tray(SystemTray::new()) .system_tray(SystemTray::new())
.setup(|app| { .setup(|app| {
resolve::resolve_setup(app); tauri::async_runtime::block_on(async move {
resolve::resolve_setup(app).await;
});
Ok(()) Ok(())
}) })
.on_system_tray_event(core::tray::Tray::on_system_tray_event) .on_system_tray_event(core::tray::Tray::on_system_tray_event)
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
// common // common
cmds::get_sys_proxy, cmds::get_sys_proxy,
cmds::get_auto_proxy,
cmds::open_app_dir, cmds::open_app_dir,
cmds::open_logs_dir, cmds::open_logs_dir,
cmds::open_web_url, cmds::open_web_url,
cmds::open_core_dir, cmds::open_core_dir,
cmds::get_portable_flag, cmds::get_portable_flag,
cmds::get_network_interfaces,
// cmds::kill_sidecar, // cmds::kill_sidecar,
cmds::restart_sidecar, cmds::restart_sidecar,
cmds::grant_permission,
// clash // clash
cmds::get_clash_info, cmds::get_clash_info,
cmds::get_clash_logs, cmds::get_clash_logs,
@@ -54,6 +64,7 @@ fn main() -> std::io::Result<()> {
cmds::get_runtime_exists, cmds::get_runtime_exists,
cmds::get_runtime_logs, cmds::get_runtime_logs,
cmds::uwp::invoke_uwp_tool, cmds::uwp::invoke_uwp_tool,
cmds::copy_clash_env,
// verge // verge
cmds::get_verge_config, cmds::get_verge_config,
cmds::patch_verge_config, cmds::patch_verge_config,
@@ -63,6 +74,7 @@ fn main() -> std::io::Result<()> {
cmds::download_icon_cache, cmds::download_icon_cache,
cmds::open_devtools, cmds::open_devtools,
cmds::exit_app, cmds::exit_app,
cmds::get_network_interfaces_info,
// cmds::update_hotkeys, // cmds::update_hotkeys,
// profile // profile
cmds::get_profiles, cmds::get_profiles,
-4
View File
@@ -88,10 +88,6 @@ pub fn profiles_path() -> Result<PathBuf> {
Ok(app_home_dir()?.join(PROFILE_YAML)) Ok(app_home_dir()?.join(PROFILE_YAML))
} }
pub fn clash_pid_path() -> Result<PathBuf> {
Ok(app_home_dir()?.join("clash.pid"))
}
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
pub fn service_path() -> Result<PathBuf> { pub fn service_path() -> Result<PathBuf> {
Ok(app_resources_dir()?.join("clash-verge-service")) Ok(app_resources_dir()?.join("clash-verge-service"))
+9 -1
View File
@@ -1,3 +1,4 @@
use crate::enhance::seq::SeqMap;
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid; use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
@@ -26,7 +27,7 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
} }
/// read mapping from yaml fix #165 /// read mapping from yaml fix #165
pub fn read_merge_mapping(path: &PathBuf) -> Result<Mapping> { pub fn read_mapping(path: &PathBuf) -> Result<Mapping> {
let mut val: Value = read_yaml(path)?; let mut val: Value = read_yaml(path)?;
val.apply_merge() val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?; .with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
@@ -40,6 +41,13 @@ pub fn read_merge_mapping(path: &PathBuf) -> Result<Mapping> {
.to_owned()) .to_owned())
} }
/// read mapping from yaml fix #165
pub fn read_seq_map(path: &PathBuf) -> Result<SeqMap> {
let val: SeqMap = read_yaml(path)?;
Ok(val)
}
/// save the data to the file /// save the data to the file
/// can set `prefix` string to add some comments /// can set `prefix` string to add some comments
pub fn save_yaml<T: Serialize>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> { pub fn save_yaml<T: Serialize>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
+3 -3
View File
@@ -137,7 +137,7 @@ pub fn delete_log() -> Result<()> {
} }
let service_log_dir = log_dir.join("service"); let service_log_dir = log_dir.join("service");
for file in fs::read_dir(&service_log_dir)?.flatten() { for file in fs::read_dir(service_log_dir)?.flatten() {
let _ = process_file(file); let _ = process_file(file);
} }
@@ -318,11 +318,11 @@ pub fn startup_script() -> Result<()> {
Some(dir) => { Some(dir) => {
let _ = Command::new(shell) let _ = Command::new(shell)
.current_dir(dir.to_path_buf()) .current_dir(dir.to_path_buf())
.args(&[path]) .args([path])
.output()?; .output()?;
} }
None => { None => {
let _ = Command::new(shell).args(&[path]).output()?; let _ = Command::new(shell).args([path]).output()?;
} }
} }
} }
+28 -33
View File
@@ -1,10 +1,6 @@
use crate::config::{IVerge, PrfOption}; use crate::cmds::import_profile;
use crate::{ use crate::config::IVerge;
config::{Config, PrfItem}, use crate::{config::Config, core::*, utils::init, utils::server};
core::*,
utils::init,
utils::server,
};
use crate::{log_err, trace_err}; use crate::{log_err, trace_err};
use anyhow::Result; use anyhow::Result;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@@ -34,7 +30,7 @@ pub fn find_unused_port() -> Result<u16> {
} }
/// handle something when start app /// handle something when start app
pub fn resolve_setup(app: &mut App) { pub async fn resolve_setup(app: &mut App) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory); app.set_activation_policy(tauri::ActivationPolicy::Accessory);
let version = app.package_info().version.to_string(); let version = app.package_info().version.to_string();
@@ -73,7 +69,8 @@ pub fn resolve_setup(app: &mut App) {
// 启动核心 // 启动核心
log::trace!("init config"); log::trace!("init config");
log_err!(Config::init_config());
log_err!(Config::init_config().await);
log::trace!("launch core"); log::trace!("launch core");
log_err!(CoreManager::global().init()); log_err!(CoreManager::global().init());
@@ -99,16 +96,20 @@ pub fn resolve_setup(app: &mut App) {
let argvs: Vec<String> = std::env::args().collect(); let argvs: Vec<String> = std::env::args().collect();
if argvs.len() > 1 { if argvs.len() > 1 {
tauri::async_runtime::block_on(async { let param = argvs[1].as_str();
resolve_scheme(argvs[1].to_owned()).await; if param.starts_with("clash:") {
}); log_err!(resolve_scheme(argvs[1].to_owned()).await);
}
} }
} }
/// reset system proxy /// reset system proxy
pub fn resolve_reset() { pub fn resolve_reset() {
log_err!(sysopt::Sysopt::global().reset_sysproxy()); log_err!(sysopt::Sysopt::global().reset_sysproxy());
log_err!(CoreManager::global().stop_core()); tauri::async_runtime::block_on(async move {
log_err!(CoreManager::global().stop_core().await);
log_err!(service::unset_dns_by_service().await);
});
} }
/// create main window /// create main window
@@ -193,11 +194,11 @@ pub fn create_window(app_handle: &AppHandle) {
} }
Ok(center) Ok(center)
})(); })();
if center.unwrap_or(true) { if center.unwrap_or(true) {
trace_err!(win.center(), "set win center"); trace_err!(win.center(), "set win center");
} }
#[cfg(not(target_os = "linux"))]
trace_err!(set_shadow(&win, true), "set win shadow"); trace_err!(set_shadow(&win, true), "set win shadow");
if is_maximized { if is_maximized {
trace_err!(win.maximize(), "set win maximize"); trace_err!(win.maximize(), "set win maximize");
@@ -205,7 +206,6 @@ pub fn create_window(app_handle: &AppHandle) {
} }
Err(_) => { Err(_) => {
log::error!("failed to create window"); log::error!("failed to create window");
return;
} }
} }
} }
@@ -236,31 +236,26 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
Ok(()) Ok(())
} }
pub async fn resolve_scheme(param: String) { pub async fn resolve_scheme(param: String) -> Result<()> {
let url = param let url = param
.trim_start_matches("clash://install-config/?url=") .trim_start_matches("clash://install-config/?url=")
.trim_start_matches("clash://install-config?url="); .trim_start_matches("clash://install-config?url=");
let option = PrfOption { match import_profile(url.to_string(), None).await {
user_agent: None, Ok(_) => {
with_proxy: Some(true),
self_proxy: None,
danger_accept_invalid_certs: None,
update_interval: None,
};
if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
if Config::profiles().data().append_item(item).is_ok() {
notification::Notification::new(crate::utils::dirs::APP_ID) notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge") .title("Clash Verge")
.body("Import profile success") .body("Import profile success")
.show() .show()
.unwrap(); .unwrap();
}; }
} else { Err(e) => {
notification::Notification::new(crate::utils::dirs::APP_ID) notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge") .title("Clash Verge")
.body("Import profile failed") .body(format!("Import profile failed: {e}"))
.show() .show()
.unwrap(); .unwrap();
log::error!("failed to parse url: {}", url); log::error!("Import profile failed: {e}");
}
} }
Ok(())
} }
+41 -22
View File
@@ -1,7 +1,10 @@
extern crate warp; extern crate warp;
use super::resolve; use super::resolve;
use crate::config::IVerge; use crate::{
config::{Config, IVerge, DEFAULT_PAC},
log_err,
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use port_scanner::local_port_available; use port_scanner::local_port_available;
use std::convert::Infallible; use std::convert::Infallible;
@@ -14,38 +17,38 @@ struct QueryParam {
} }
/// check whether there is already exists /// check whether there is already exists
pub fn check_singleton() -> Result<()> { pub async fn check_singleton() -> Result<()> {
let port = IVerge::get_singleton_port(); let port = IVerge::get_singleton_port();
if !local_port_available(port) { if !local_port_available(port) {
tauri::async_runtime::block_on(async { let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping"))
let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping")) .await?
.await? .text()
.text() .await?;
.await?;
if &resp == "ok" { if &resp == "ok" {
let argvs: Vec<String> = std::env::args().collect(); let argvs: Vec<String> = std::env::args().collect();
if argvs.len() > 1 { if argvs.len() > 1 {
let param = argvs[1].as_str(); let param = argvs[1].as_str();
if param.starts_with("clash:") {
reqwest::get(format!( reqwest::get(format!(
"http://127.0.0.1:{port}/commands/scheme?param={param}" "http://127.0.0.1:{port}/commands/scheme?param={param}"
)) ))
.await? .await?
.text() .text()
.await?; .await?;
} else {
reqwest::get(format!("http://127.0.0.1:{port}/commands/visible"))
.await?
.text()
.await?;
} }
bail!("app exists"); } else {
reqwest::get(format!("http://127.0.0.1:{port}/commands/visible"))
.await?
.text()
.await?;
} }
bail!("app exists");
}
log::error!("failed to setup singleton listen server"); log::error!("failed to setup singleton listen server");
Ok(()) Ok(())
})
} else { } else {
Ok(()) Ok(())
} }
@@ -64,15 +67,31 @@ pub fn embed_server(app_handle: AppHandle) {
"ok" "ok"
}); });
let pac = warp::path!("commands" / "pac").map(move || {
let content = Config::verge()
.latest()
.pac_file_content
.clone()
.unwrap_or(DEFAULT_PAC.to_string());
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let content = content.replace("%mixed-port%", &format!("{}", port));
warp::http::Response::builder()
.header("Content-Type", "application/x-ns-proxy-autoconfig")
.body(content)
.unwrap_or_default()
});
let scheme = warp::path!("commands" / "scheme") let scheme = warp::path!("commands" / "scheme")
.and(warp::query::<QueryParam>()) .and(warp::query::<QueryParam>())
.and_then(scheme_handler); .and_then(scheme_handler);
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> { async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
resolve::resolve_scheme(query.param).await; log_err!(resolve::resolve_scheme(query.param).await);
Ok("ok") Ok("ok")
} }
let commands = ping.or(visible).or(scheme); let commands = ping.or(visible).or(pac).or(scheme);
warp::serve(commands).run(([127, 0, 0, 1], port)).await; warp::serve(commands).run(([127, 0, 0, 1], port)).await;
}); });
} }
+37 -17
View File
@@ -13,31 +13,51 @@ rules: []
/// enhanced profile /// enhanced profile
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge
prepend-rules: [] profile:
store-selected: true
prepend-rule-providers: {} dns:
use-system-hosts: false
";
prepend-proxies: [] pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Clash Verge
prepend-proxy-providers: {}
prepend-proxy-groups: []
append-rules: []
append-rule-providers: {}
append-proxies: []
append-proxy-providers: {}
append-proxy-groups: []
"; ";
/// enhanced profile /// enhanced profile
pub const ITEM_SCRIPT: &str = "// Define main function (script entry) pub const ITEM_SCRIPT: &str = "// Define main function (script entry)
function main(config) { function main(config, profileName) {
return config; return config;
} }
"; ";
/// enhanced profile
pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Clash Verge
prepend: []
append: []
delete: []
";
/// enhanced profile
pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Clash Verge
prepend: []
append: []
delete: []
";
/// enhanced profile
pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Clash Verge
prepend: []
append: []
delete: []
";
+7 -6
View File
@@ -2,7 +2,7 @@
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": { "package": {
"productName": "Clash Verge", "productName": "Clash Verge",
"version": "1.6.1" "version": "1.7.4"
}, },
"build": { "build": {
"distDir": "../dist", "distDir": "../dist",
@@ -18,12 +18,13 @@
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
"icons/128x128@2x.png", "icons/128x128@2x.png",
"icons/icon-new.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"resources": ["resources"], "resources": ["resources"],
"externalBin": ["sidecar/clash-meta", "sidecar/clash-meta-alpha"], "publisher": "Clash Verge Rev",
"copyright": "© 2022 zzzgydi All Rights Reserved", "externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
"copyright": "GNU General Public License v3.0",
"category": "DeveloperTool", "category": "DeveloperTool",
"shortDescription": "A Clash Meta GUI based on tauri.", "shortDescription": "A Clash Meta GUI based on tauri.",
"longDescription": "A Clash Meta GUI based on tauri." "longDescription": "A Clash Meta GUI based on tauri."
@@ -62,7 +63,7 @@
}, },
"protocol": { "protocol": {
"asset": true, "asset": true,
"assetScope": ["$APPDATA/**", "$RESOURCE/../**"] "assetScope": ["$APPDATA/**", "$RESOURCE/../**", "**"]
}, },
"path": { "path": {
"all": true "all": true
@@ -75,7 +76,7 @@
}, },
"windows": [], "windows": [],
"security": { "security": {
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src asset: http: https: data: 'self';" "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: asset: 'unsafe-eval' 'unsafe-inline' 'self';"
} }
} }
} }
+13 -3
View File
@@ -2,14 +2,24 @@
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": { "tauri": {
"systemTray": { "systemTray": {
"iconPath": "icons/tray-icon.png" "iconPath": "icons/tray-icon.ico"
}, },
"bundle": { "bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev", "identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["deb", "appimage", "updater"], "targets": ["deb", "rpm"],
"deb": { "deb": {
"depends": ["openssl"], "depends": ["openssl"],
"desktopTemplate": "./template/clash-verge.desktop" "desktopTemplate": "./template/clash-verge.desktop",
"provides": ["clash-verge"],
"conflicts": ["clash-verge"],
"replaces": ["clash-verge"]
},
"rpm": {
"depends": ["openssl"],
"desktopTemplate": "./template/clash-verge.desktop",
"provides": ["clash-verge"],
"conflicts": ["clash-verge"],
"obsoletes": ["clash-verge"]
} }
} }
} }
+20 -1
View File
@@ -2,7 +2,7 @@
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": { "tauri": {
"systemTray": { "systemTray": {
"iconPath": "icons/mac-tray-icon.png", "iconPath": "icons/tray-icon-mono.ico",
"iconAsTemplate": true "iconAsTemplate": true
}, },
"bundle": { "bundle": {
@@ -14,6 +14,25 @@
"exceptionDomain": "", "exceptionDomain": "",
"signingIdentity": null, "signingIdentity": null,
"entitlements": null "entitlements": null
},
"dmg": {
"background": "images/background.png",
"appPosition": {
"x": 180,
"y": 170
},
"applicationFolderPosition": {
"x": 480,
"y": 170
},
"windowSize": {
"height": 400,
"width": 660
},
"windowPosition": {
"x": 200,
"y": 180
}
} }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": { "tauri": {
"systemTray": { "systemTray": {
"iconPath": "icons/tray-icon.png" "iconPath": "icons/tray-icon.ico"
}, },
"bundle": { "bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev", "identifier": "io.github.clash-verge-rev.clash-verge-rev",
+150 -84
View File
@@ -13,8 +13,9 @@ Unicode true
!include FileFunc.nsh !include FileFunc.nsh
!include x64.nsh !include x64.nsh
!include WordFunc.nsh !include WordFunc.nsh
!include "LogicLib.nsh"
!include "StrFunc.nsh" !include "StrFunc.nsh"
!include "Win\COM.nsh"
!include "Win\Propkey.nsh"
!addplugindir "$%AppData%\Local\NSIS\" !addplugindir "$%AppData%\Local\NSIS\"
${StrCase} ${StrCase}
${StrLoc} ${StrLoc}
@@ -149,7 +150,6 @@ Function PageReinstall
; however, this should be fine since the user will have to confirm the uninstallation ; however, this should be fine since the user will have to confirm the uninstallation
; and they can chose to abort it if doesn't make sense. ; and they can chose to abort it if doesn't make sense.
StrCpy $0 0 StrCpy $0 0
wix_loop: wix_loop:
EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0 EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on
@@ -323,10 +323,15 @@ Var AppStartMenuFolder
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)" !define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut !define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
; Show run app after installation. ; Show run app after installation.
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe" !define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
Function RunMainBinary
nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" ""
FunctionEnd
; Uninstaller Pages ; Uninstaller Pages
; 1. Confirm uninstall page ; 1. Confirm uninstall page
Var DeleteAppDataCheckbox Var DeleteAppDataCheckbox
@@ -420,55 +425,85 @@ Function .onInit
FunctionEnd FunctionEnd
!macro CheckAllVergeProcesses !macro CheckAllVergeProcesses
; Check if Clash Verge.exe is running ; Check if clash-verge-service.exe is running
nsis_tauri_utils::FindProcess "Clash Verge.exe" !if "${INSTALLMODE}" == "currentUser"
${If} $R0 != 0 nsis_tauri_utils::FindProcessCurrentUser "clash-verge-service.exe"
; Kill the process !else
DetailPrint "Kill Clash Verge.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "Clash Verge.exe"
!else
nsis_tauri_utils::KillProcess "Clash Verge.exe"
!endif
${EndIf}
; Check if clash-verge-service.exe is running
nsis_tauri_utils::FindProcess "clash-verge-service.exe" nsis_tauri_utils::FindProcess "clash-verge-service.exe"
${If} $R0 != 0 !endif
; Kill the process Pop $R0
DetailPrint "Kill clash-verge-service.exe..." ${If} $R0 = 0
!if "${INSTALLMODE}" == "currentUser" DetailPrint "Kill clash-verge-service.exe..."
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe" !if "${INSTALLMODE}" == "currentUser"
!else nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
nsis_tauri_utils::KillProcess "clash-verge-service.exe" !else
!endif nsis_tauri_utils::KillProcess "clash-verge-service.exe"
${EndIf} !endif
${EndIf}
; Check if verge-mihomo-alpha.exe is running
; Check if clash-meta-alpha.exe is running !if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo-alpha.exe"
!else
nsis_tauri_utils::FindProcess "verge-mihomo-alpha.exe"
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill verge-mihomo-alpha.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo-alpha.exe"
!else
nsis_tauri_utils::KillProcess "verge-mihomo-alpha.exe"
!endif
${EndIf}
; Check if verge-mihomo.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "verge-mihomo.exe"
!else
nsis_tauri_utils::FindProcess "verge-mihomo.exe"
!endif
Pop $R0
${If} $R0 = 0
DetailPrint "Kill verge-mihomo.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "verge-mihomo.exe"
!else
nsis_tauri_utils::KillProcess "verge-mihomo.exe"
!endif
${EndIf}
; Check if clash-meta-alpha.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "clash-meta-alpha.exe"
!else
nsis_tauri_utils::FindProcess "clash-meta-alpha.exe" nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
${If} $R0 != 0 !endif
; Kill the process Pop $R0
DetailPrint "Kill clash-meta-alpha.exe..." ${If} $R0 = 0
!if "${INSTALLMODE}" == "currentUser" DetailPrint "Kill clash-meta-alpha.exe..."
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe" !if "${INSTALLMODE}" == "currentUser"
!else nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe" !else
!endif nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
${EndIf} !endif
${EndIf}
; Check if clash-meta.exe is running ; Check if clash-meta.exe is running
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "clash-meta.exe"
!else
nsis_tauri_utils::FindProcess "clash-meta.exe" nsis_tauri_utils::FindProcess "clash-meta.exe"
${If} $R0 != 0 !endif
; Kill the process Pop $R0
DetailPrint "Kill clash-meta.exe..." ${If} $R0 = 0
!if "${INSTALLMODE}" == "currentUser" DetailPrint "Kill clash-meta.exe..."
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe" !if "${INSTALLMODE}" == "currentUser"
!else nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
nsis_tauri_utils::KillProcess "clash-meta.exe" !else
!endif nsis_tauri_utils::KillProcess "clash-meta.exe"
${EndIf} !endif
${EndIf}
!macroend !macroend
!macro StartVergeService !macro StartVergeService
@@ -571,7 +606,7 @@ Section WebView2
!if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper" !if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
DetailPrint "$(webview2Downloading)" DetailPrint "$(webview2Downloading)"
nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe" NSISdl::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
Pop $0 Pop $0
${If} $0 == 0 ${If} $0 == 0
DetailPrint "$(webview2DownloadSuccess)" DetailPrint "$(webview2DownloadSuccess)"
@@ -730,7 +765,8 @@ Function .onInstSuccess
check_r_flag: check_r_flag:
${GetOptions} $CMDLINE "/R" $R0 ${GetOptions} $CMDLINE "/R" $R0
IfErrors run_done 0 IfErrors run_done 0
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"' ${GetOptions} $CMDLINE "/ARGS" $R0
nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" "$R0"
run_done: run_done:
FunctionEnd FunctionEnd
@@ -743,30 +779,35 @@ Function un.onInit
!insertmacro MUI_UNGETLANGUAGE !insertmacro MUI_UNGETLANGUAGE
FunctionEnd FunctionEnd
Function un.isDirectoryEmpty !macro DeleteAppUserModelId
Exch $0 !insertmacro ComHlpr_CreateInProcInstance ${CLSID_DestinationList} ${IID_ICustomDestinationList} r1 ""
Push $1 ${If} $1 P<> 0
Push $2 ${ICustomDestinationList::DeleteList} $1 '("${BUNDLEID}")'
StrCpy $2 0 ${IUnknown::Release} $1 ""
FindFirst $1 $2 "$0\*.*" ${EndIf}
loop: !insertmacro ComHlpr_CreateInProcInstance ${CLSID_ApplicationDestinations} ${IID_IApplicationDestinations} r1 ""
StrCmp $2 "" done ${If} $1 P<> 0
StrCmp $2 "." next ${IApplicationDestinations::SetAppID} $1 '("${BUNDLEID}")i.r0'
StrCmp $2 ".." next ${If} $0 >= 0
StrCpy $0 0 ${IApplicationDestinations::RemoveAllDestinations} $1 ''
goto done ${EndIf}
next: ${IUnknown::Release} $1 ""
FindNext $1 $2 ${EndIf}
goto loop !macroend
done:
FindClose $1 ; From https://stackoverflow.com/a/42816728/16993372
StrCmp $2 "" 0 +2 !macro UnpinShortcut shortcut
StrCpy $0 1 !insertmacro ComHlpr_CreateInProcInstance ${CLSID_StartMenuPin} ${IID_IStartMenuPinnedList} r0 ""
Pop $2 ${If} $0 P<> 0
Pop $1 System::Call 'SHELL32::SHCreateItemFromParsingName(ws, p0, g "${IID_IShellItem}", *p0r1)' "${shortcut}"
Exch $0 ${If} $1 P<> 0
FunctionEnd ${IStartMenuPinnedList::RemoveFromList} $0 '(r1)'
${IUnknown::Release} $1 ""
${EndIf}
${IUnknown::Release} $0 ""
${EndIf}
!macroend
Section Uninstall Section Uninstall
!insertmacro CheckIfAppIsRunning !insertmacro CheckIfAppIsRunning
@@ -789,15 +830,14 @@ Section Uninstall
; Delete uninstaller ; Delete uninstaller
Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\uninstall.exe"
; Remove InstallDir {{#each resources_ancestors}}
Push "$INSTDIR" RMDir /REBOOTOK "$INSTDIR\\{{this}}"
Call un.isDirectoryEmpty {{/each}}
Pop $0 RMDir "$INSTDIR"
${If} $0 == 1
RMDir /R /REBOOTOK "$INSTDIR" !insertmacro DeleteAppUserModelId
${Else} !insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
MessageBox MB_OK "Install Directory is not Empty, Please remove it manually." !insertmacro UnpinShortcut "$DESKTOP\${MAINBINARYNAME}.lnk"
${EndIf}
; Remove start menu shortcut ; Remove start menu shortcut
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
@@ -840,13 +880,39 @@ Function SkipIfPassive
${IfThen} $PassiveMode == 1 ${|} Abort ${|} ${IfThen} $PassiveMode == 1 ${|} Abort ${|}
FunctionEnd FunctionEnd
!macro SetLnkAppUserModelId shortcut
!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ShellLink} ${IID_IShellLink} r0 ""
${If} $0 P<> 0
${IUnknown::QueryInterface} $0 '("${IID_IPersistFile}",.r1)'
${If} $1 P<> 0
${IPersistFile::Load} $1 '("${shortcut}", ${STGM_READWRITE})'
${IUnknown::QueryInterface} $0 '("${IID_IPropertyStore}",.r2)'
${If} $2 P<> 0
System::Call 'Oleaut32::SysAllocString(w "${BUNDLEID}") i.r3'
System::Call '*${SYSSTRUCT_PROPERTYKEY}(${PKEY_AppUserModel_ID})p.r4'
System::Call '*${SYSSTRUCT_PROPVARIANT}(${VT_BSTR},,&i4 $3)p.r5'
${IPropertyStore::SetValue} $2 '($4,$5)'
System::Call 'Oleaut32::SysFreeString($3)'
System::Free $4
System::Free $5
${IPropertyStore::Commit} $2 ""
${IUnknown::Release} $2 ""
${IPersistFile::Save} $1 '("${shortcut}",1)'
${EndIf}
${IUnknown::Release} $1 ""
${EndIf}
${IUnknown::Release} $0 ""
${EndIf}
!macroend
Function CreateDesktopShortcut Function CreateDesktopShortcut
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" !insertmacro SetLnkAppUserModelId "$DESKTOP\${MAINBINARYNAME}.lnk"
FunctionEnd FunctionEnd
Function CreateStartMenuShortcut Function CreateStartMenuShortcut
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}" !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
FunctionEnd FunctionEnd
+38
View File
@@ -0,0 +1,38 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"systemTray": {
"iconPath": "icons/tray-icon.ico"
},
"bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["nsis", "updater"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"webviewInstallMode": {
"type": "fixedRuntime",
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.arm64/"
},
"nsis": {
"displayLanguageSelector": true,
"installerIcon": "icons/icon.ico",
"languages": ["SimpChinese", "English"],
"license": "../LICENSE",
"installMode": "perMachine",
"template": "./template/installer.nsi"
}
}
},
"updater": {
"active": true,
"dialog": false,
"endpoints": [
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"systemTray": {
"iconPath": "icons/tray-icon.ico"
},
"bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["nsis", "updater"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"webviewInstallMode": {
"type": "fixedRuntime",
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x64/"
},
"nsis": {
"displayLanguageSelector": true,
"installerIcon": "icons/icon.ico",
"languages": ["SimpChinese", "English"],
"license": "../LICENSE",
"installMode": "perMachine",
"template": "./template/installer.nsi"
}
}
},
"updater": {
"active": true,
"dialog": false,
"endpoints": [
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"systemTray": {
"iconPath": "icons/tray-icon.ico"
},
"bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["nsis", "updater"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"webviewInstallMode": {
"type": "fixedRuntime",
"path": "./Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.x86/"
},
"nsis": {
"displayLanguageSelector": true,
"installerIcon": "icons/icon.ico",
"languages": ["SimpChinese", "English"],
"license": "../LICENSE",
"installMode": "perMachine",
"template": "./template/installer.nsi"
}
}
},
"updater": {
"active": true,
"dialog": false,
"endpoints": [
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-fixed-webview2.json"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M 175.755 768.851 L 334.113 343.826 L 394.912 343.826 L 554.501 768.851 L 494.851 768.851 L 453.989 653.897 L 275.036 653.897 L 234.583 768.851 L 175.755 768.851 Z M 293.332 604.339 L 436.102 604.339 L 366.441 406.757 L 362.994 406.757 L 293.332 604.339 Z M 703.344 780.174 C 670.415 780.174 644.541 771.518 625.724 754.205 C 606.907 736.892 597.499 713.412 597.499 683.764 C 597.499 653.898 608.575 629.61 630.729 610.902 C 652.882 592.195 681.682 582.842 717.129 582.842 C 732.663 582.842 747.433 584.223 761.437 586.985 C 775.44 589.747 787.419 593.973 797.375 599.662 L 797.375 580.626 C 797.375 557.323 790.66 539.299 777.231 526.554 C 763.802 513.808 744.753 507.435 720.082 507.435 C 705.423 507.435 691.652 510.293 678.769 516.01 C 665.887 521.726 654.004 530.218 643.118 541.486 L 607.099 512.687 C 621.43 495.948 637.95 483.422 656.657 475.107 C 675.365 466.793 696.726 462.636 720.739 462.636 C 762.639 462.636 794.365 472.988 815.918 493.693 C 837.469 514.397 848.245 544.797 848.245 584.893 L 848.245 770.574 L 798.031 770.574 L 798.031 731.025 L 794.421 731.025 C 784.903 746.889 772.39 759.046 756.882 767.498 C 741.375 775.948 723.529 780.174 703.344 780.174 Z M 709.087 736.688 C 734.031 736.688 754.981 727.594 771.939 709.406 C 788.896 691.218 797.375 668.694 797.375 641.836 C 787.693 636.147 776.315 631.812 763.242 628.831 C 750.168 625.849 737.751 624.358 725.99 624.358 C 701.594 624.358 682.735 629.555 669.415 639.949 C 656.096 650.342 649.436 664.947 649.436 683.764 C 649.436 699.628 654.906 712.414 665.846 722.124 C 676.786 731.833 691.2 736.688 709.087 736.688 Z"
p-id="11663" transform="matrix(1, 0, 0, 1, 0, 3.552713678800501e-15)" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M 64.002 831.066 L 64.002 649.735 L 128 649.735 L 128 767.067 L 896 767.067 L 896 649.735 L 959.999 649.735 L 959.999 831.066 L 64.001 831.066 L 64.002 831.066 Z M 414.031 680.257 L 414.031 640.708 L 410.421 640.708 C 400.903 656.571 388.39 668.729 372.882 677.18 C 357.375 685.631 339.529 689.857 319.344 689.857 C 286.688 689.857 260.883 681.2 241.929 663.887 C 222.975 646.575 213.499 623.095 213.499 593.447 C 213.499 563.58 224.575 539.293 246.729 520.585 C 268.883 501.878 297.546 492.524 332.719 492.524 C 348.254 492.524 363.023 493.905 377.026 496.667 C 391.029 499.43 403.146 503.656 413.375 509.345 L 413.375 490.309 C 413.375 467.006 406.729 448.982 393.437 436.236 C 380.144 423.49 361.299 417.117 336.902 417.117 C 321.969 417.117 307.993 419.976 294.975 425.692 C 281.956 431.408 270.004 439.9 259.118 451.169 L 223.099 422.37 C 237.43 405.631 253.95 393.104 272.657 384.79 C 291.365 376.476 312.862 372.318 337.149 372.318 C 379.05 372.318 410.708 382.671 432.123 403.375 C 453.538 424.079 464.245 454.479 464.245 494.575 L 464.245 680.257 L 414.031 680.257 Z M 341.99 534.041 C 317.867 534.041 299.077 539.238 285.62 549.631 C 272.164 560.024 265.436 574.493 265.436 593.036 C 265.436 609.174 270.974 622.097 282.051 631.806 C 293.128 641.516 307.61 646.371 325.498 646.371 C 350.441 646.371 371.323 637.277 388.144 619.089 C 404.965 600.9 413.375 578.377 413.375 551.518 C 403.693 545.83 392.315 541.495 379.242 538.514 C 366.168 535.532 353.751 534.041 341.99 534.041 Z M 541.375 680.667 L 541.375 252.934 L 593.558 252.934 L 593.558 384.545 L 590.358 427.211 L 593.148 427.211 C 598.564 418.733 608.943 408.435 624.287 396.319 C 639.631 384.203 660.977 378.145 688.327 378.145 C 729.735 378.145 762.638 393.27 787.035 423.519 C 811.431 453.769 823.629 491.047 823.629 535.355 C 823.629 579.116 811.609 615.874 787.568 645.631 C 763.527 675.389 730.31 690.267 687.917 690.267 C 662.044 690.267 641.258 684.688 625.558 673.529 C 609.859 662.37 599.056 651.594 593.148 641.201 L 590.358 641.201 L 590.358 680.667 L 541.375 680.667 Z M 681.6 426.801 C 653.702 426.801 631.521 437.563 615.056 459.087 C 598.591 480.613 590.358 505.816 590.358 534.698 C 590.358 564.018 598.591 589.263 615.056 610.433 C 631.521 631.602 653.702 642.186 681.6 642.186 C 709.497 642.186 731.309 631.807 747.036 611.048 C 762.762 590.289 770.625 564.839 770.625 534.698 C 770.625 504.558 762.762 479.04 747.036 458.145 C 731.309 437.249 709.497 426.801 681.6 426.801 Z"
p-id="12920" transform="matrix(1, 0, 0, 1, 0, 1.4210854715202004e-14)" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M 838.757 427.686 L 808.603 338.337 L 633.84 409.02 L 645.314 217.721 L 549.541 217.721 L 561.015 409.02 L 386.252 338.337 L 356.098 427.686 L 539.205 477.619 L 413.277 620.027 L 487.146 678.203 L 597.428 519.119 L 706.666 678.203 L 780.535 620.027 L 654.607 477.619 L 838.757 427.686 Z"
p-id="15003" style="transform-origin: 597.428px 447.962px;" />
<path
d="M 185.244 735.674 C 185.244 789.945 243.994 823.864 290.994 796.729 C 312.807 784.135 326.244 760.861 326.244 735.674 C 326.244 681.403 267.494 647.484 220.494 674.619 C 198.681 687.213 185.244 710.487 185.244 735.674 Z"
p-id="15004" style="transform-origin: 255.744px 735.674px;" />
</svg>

After

Width:  |  Height:  |  Size: 803 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 512.00 512.00"
width="512.00" height="512.00">
<path fill="#ffffff"
d="&#10; M 45.65 297.77&#10; C 50.31 280.20 56.48 263.74 64.10 247.67&#10; C 66.07 243.51 65.99 238.92 65.73 234.44&#10; Q 63.32 191.66 64.13 148.11&#10; C 64.39 134.30 66.21 57.13 87.19 55.68&#10; Q 94.10 55.20 99.69 57.92&#10; Q 109.79 62.83 121.61 72.92&#10; C 143.03 91.20 162.36 112.69 183.36 130.92&#10; C 186.54 133.67 193.35 138.99 197.10 138.76&#10; Q 198.57 138.67 200.07 138.26&#10; Q 222.12 132.30 238.17 131.29&#10; C 256.93 130.11 277.29 130.43 296.13 134.49&#10; Q 305.05 136.41 313.24 138.56&#10; C 318.37 139.92 325.54 133.72 329.51 130.33&#10; Q 339.55 121.75 347.99 113.37&#10; C 364.54 96.94 380.60 80.49 399.04 66.35&#10; C 403.86 62.66 409.75 58.98 415.36 56.95&#10; C 419.03 55.63 425.97 54.84 429.34 57.30&#10; C 433.77 60.52 436.73 66.64 438.49 71.86&#10; Q 441.73 81.45 443.26 90.82&#10; Q 447.60 117.52 448.00 151.51&#10; Q 448.45 189.74 447.59 207.00&#10; Q 446.70 225.12 446.21 240.03&#10; C 446.12 242.85 446.89 245.55 448.09 248.07&#10; Q 459.73 272.71 466.27 297.70&#10; C 467.59 302.75 468.45 308.08 467.82 313.31&#10; C 466.21 326.87 459.76 339.57 452.24 350.80&#10; Q 436.72 374.00 411.37 395.51&#10; Q 374.63 426.67 330.92 443.23&#10; Q 272.04 465.54 211.41 452.34&#10; Q 188.54 447.36 165.13 436.61&#10; Q 121.44 416.54 86.71 382.78&#10; Q 69.63 366.18 57.73 347.55&#10; C 50.80 336.69 44.86 323.90 44.03 311.09&#10; Q 43.65 305.29 45.65 297.77&#10; Z&#10; M 131.34 313.94&#10; C 140.29 332.22 157.72 341.20 177.30 342.68&#10; Q 184.65 343.24 193.22 340.65&#10; Q 202.03 338.00 205.56 330.26&#10; C 211.13 318.09 200.76 303.01 191.81 296.02&#10; C 179.37 286.31 161.98 280.10 146.19 280.97&#10; Q 137.21 281.47 131.35 287.14&#10; C 124.01 294.24 127.17 305.43 131.34 313.94&#10; Z&#10; M 349.22 281.81&#10; C 332.78 284.95 316.93 292.71 307.08 305.92&#10; C 303.14 311.22 300.42 317.96 301.07 324.43&#10; C 302.18 335.36 310.18 340.08 320.43 341.92&#10; C 336.31 344.78 355.06 339.00 366.59 328.03&#10; C 376.14 318.95 389.80 294.29 373.19 284.22&#10; C 366.55 280.20 356.95 280.33 349.22 281.81&#10; Z&#10; M 226.25 381.62&#10; C 232.99 389.35 240.71 395.69 249.97 398.50&#10; C 259.93 401.51 272.87 391.21 279.39 384.18&#10; C 281.43 381.98 283.70 379.66 284.61 376.72&#10; C 285.41 374.13 282.30 371.54 280.28 370.59&#10; Q 276.07 368.62 271.56 368.03&#10; Q 254.57 365.79 237.08 367.97&#10; Q 232.61 368.53 228.23 370.40&#10; C 225.86 371.41 222.31 374.22 223.50 377.19&#10; Q 224.45 379.55 226.25 381.62&#10; Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 512.00 512.00"
width="512.00" height="512.00">
<path fill="#000000"
d="&#10; M 66.75 243.26&#10; C 64.36 202.61 63.47 160.98 66.14 119.90&#10; Q 67.07 105.54 69.56 90.86&#10; C 71.35 80.37 74.26 67.20 81.13 59.68&#10; C 88.86 51.20 102.34 59.42 109.45 64.46&#10; Q 122.61 73.79 137.56 88.26&#10; Q 154.16 104.32 170.37 120.15&#10; Q 177.39 127.01 185.78 133.69&#10; C 189.58 136.71 194.75 140.48 199.81 139.03&#10; Q 256.12 122.89 312.63 139.17&#10; C 317.17 140.47 322.43 136.89 326.29 133.81&#10; Q 334.64 127.18 341.86 120.15&#10; Q 358.44 104.02 373.87 89.06&#10; Q 389.67 73.75 403.99 63.72&#10; Q 409.86 59.61 416.68 57.20&#10; C 430.17 52.45 435.71 64.65 438.76 74.78&#10; Q 442.82 88.24 444.57 104.64&#10; Q 447.71 133.95 447.66 168.99&#10; Q 447.61 205.59 445.24 243.61&#10; Q 445.21 244.12 445.44 244.57&#10; Q 459.30 271.43 466.56 302.09&#10; C 469.00 312.41 465.64 324.20 461.06 333.82&#10; C 449.65 357.80 430.14 378.99 409.62 396.13&#10; Q 372.77 426.90 329.61 443.00&#10; Q 266.07 466.70 201.80 449.27&#10; C 162.55 438.62 125.61 417.06 95.28 389.88&#10; C 77.45 373.90 60.60 354.63 50.57 332.92&#10; C 46.30 323.66 43.03 312.16 45.33 302.37&#10; Q 52.57 271.58 66.46 244.63&#10; Q 66.80 243.98 66.75 243.26&#10; Z&#10; M 129.31 310.72&#10; Q 136.38 328.58 152.74 336.68&#10; C 165.31 342.91 181.44 345.53 194.60 340.75&#10; C 211.72 334.54 209.96 316.29 200.74 304.29&#10; C 190.53 291.00 173.63 283.30 157.10 280.73&#10; C 136.41 277.52 120.03 287.25 129.31 310.72&#10; Z&#10; M 304.10 309.36&#10; C 297.35 321.61 299.56 335.79 313.93 340.88&#10; C 326.42 345.31 342.09 343.01 354.08 337.35&#10; Q 374.66 327.63 380.68 304.95&#10; C 386.50 282.97 365.69 278.03 349.30 281.14&#10; C 331.39 284.54 312.80 293.56 304.10 309.36&#10; Z&#10; M 244.39 396.99&#10; Q 252.76 401.23 260.59 398.28&#10; Q 271.64 394.13 281.68 382.89&#10; C 290.72 372.77 280.23 368.82 272.04 367.56&#10; Q 253.06 364.63 234.76 367.80&#10; C 228.71 368.85 218.66 372.23 224.67 380.57&#10; Q 231.98 390.72 244.39 396.99&#10; Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

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