Compare commits

..
665 Commits
Author SHA1 Message Date
MystiPanda 3271c96531 fix: enhance when change tun config 2024-05-04 16:12:10 +08:00
dongchengjie 05fa6b9aba chore: profile template typo 2024-05-04 14:24:11 +08:00
MystiPanda 7edd7f27cb fix: try to set env 2024-05-03 18:00:55 +08:00
dongchengjie 4a6b12eda1 fix: #974 2024-05-03 12:50:04 +08:00
MystiPanda a354e1a0eb chore: update locale 2024-05-02 23:44:09 +08:00
dongchengjie c2c419522a chore: add service mode locale 2024-05-02 22:16:05 +08:00
MystiPanda 7a3cc7d242 feat: Support drag and drop local files 2024-05-02 20:41:43 +08:00
MystiPanda bd6f02f6af fix: change script execution path 2024-05-01 11:39:09 +08:00
MystiPanda bd33728fd7 Release 1.6.1 2024-04-30 23:43:51 +08:00
MystiPanda 8400ee8a3d fix: MacOS tray click 2024-04-30 23:21:25 +08:00
lxk955 80ee7aef8e feat: Support for using regular expressions on the log page #707 (#959) 2024-04-30 22:59:42 +08:00
MystiPanda 41762be3a5 fix: service install path 2024-04-30 20:20:39 +08:00
MystiPanda 605dda7b76 build: Restore core version 2024-04-30 09:42:25 +08:00
dongchengjie 8d6e3b5a58 fixup: wrong version 2024-04-30 02:13:24 +08:00
dongchengjie 53920fc368 chore: update pnpm-lock.yaml (#952)
* chore: update schema version

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

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

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

* chore: tauri.conf.json json schema

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

* feat: fixed proxy indicator

* fix: try to fix traffic websocket no longer updating

* fixup: group delay test use defined url

* feat: connections sorted by start by default

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

* fix: editor no hints and add yaml support

* feat: quick suggestions

* chore: use monaco-editor

* chore: update schema url

* chore: change default merge config content

* fix: load schema via npm

* feat: runtime config viewer style auto adjust

* feat: adjust fixed proxy style

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

* chore: switch version

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

* feat: fixed proxy indicator

* fix: try to fix traffic websocket no longer updating

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

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

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

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

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

* chore: cleanup

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

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

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

* Add Russian support

* Minor update

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

* runfix: Add children type to FC components

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

* use package from source
2023-01-18 23:36:18 +08:00
GyDi bba03d14d4 fix: compatible with UTF8 BOM, close #283 2023-01-17 19:51:02 +08:00
GyDi 23b728a762 feat: adjust macOS window style 2023-01-16 22:57:53 +08:00
GyDi 819c5207d2 fix: use selected proxy after profile changed 2023-01-15 21:33:03 +08:00
GyDi 311358544e fix: error log 2023-01-15 19:11:51 +08:00
GyDi 4480ecc96d chore: ci 2023-01-14 22:22:18 +08:00
GyDi 6c5f70a205 v1.2.2 2023-01-14 22:01:28 +08:00
GyDi 99adfb4a9e chore: update log 2023-01-14 21:56:27 +08:00
GyDi 7909cf4067 fix: adjust fields order 2023-01-14 14:57:55 +08:00
GyDi 780ab20aeb fix: add meta fields 2023-01-14 14:51:29 +08:00
GyDi 73119bb7c5 chore: ci 2023-01-14 12:20:17 +08:00
GyDi f20f0f064e chore: update clash meta 2023-01-14 12:16:09 +08:00
GyDi 1b44ae098c fix: add os platform value 2023-01-14 12:07:31 +08:00
GyDi 453c230716 feat: recover core after panic, close #353 2023-01-14 11:45:47 +08:00
GyDi 439d885ee1 chore: allow unused 2023-01-13 23:11:48 +08:00
GyDi 43dee3ef76 fix: reconnect traffic websocket 2023-01-13 23:02:45 +08:00
GyDi c71ba6ff8d chore: update deps 2023-01-13 21:29:21 +08:00
GyDi fb7a36eb73 feat: use decorations in Linux, close #354 2023-01-12 00:42:33 +08:00
GyDi e7f294a065 fix: parse bytes precision, close #334 2023-01-11 14:05:49 +08:00
GyDi d5037f180e fix: trigger new profile dialog, close #356 2023-01-11 13:45:16 +08:00
GyDi e90158809a fix: parse log cause panic 2023-01-11 13:30:14 +08:00
GyDi 0cb802ed9a chore: update clash meta 2023-01-09 21:55:03 +08:00
GyDi d0b47204f4 v1.2.1 2022-12-23 22:44:27 +08:00
GyDi 351cb391e5 chore: update log 2022-12-23 22:44:00 +08:00
GyDi 051be927cd fix: avoid setting login item repeatedly, close #326 2022-12-23 22:39:27 +08:00
GyDi 8bad2c2113 fix: adjust code 2022-12-15 21:54:48 +08:00
GyDi 2bcf6fb3eb fix: adjust delay check concurrency 2022-12-15 12:23:57 +08:00
GyDi d1ba0ed2b2 chore: adjust code 2022-12-15 12:22:20 +08:00
GyDi 6e421e60c5 fix: change default column to auto 2022-12-14 16:56:33 +08:00
GyDi 8385050804 fix: change default app version 2022-12-14 16:15:53 +08:00
GyDi bfe4f08232 fix: adjust rule ui 2022-12-14 15:59:41 +08:00
GyDi 132f914b0d fix: adjust log ui 2022-12-14 15:49:05 +08:00
GyDi 97d82b03ab fix: keep delay data 2022-12-14 15:29:02 +08:00
GyDi f06fa3f9b7 fix: use list item button 2022-12-14 15:16:49 +08:00
GyDi 6337788a22 fix: proxy item style 2022-12-14 15:15:44 +08:00
GyDi 024db4358b feat: auto proxy layout column 2022-12-14 15:07:51 +08:00
GyDi 173f35487e chore: fix ci 2022-12-13 18:13:28 +08:00
GyDi 74cbe82dd1 chore: fix ci 2022-12-13 18:11:43 +08:00
GyDi a8c30d30a9 chore: fix ci 2022-12-13 18:02:23 +08:00
GyDi 1b7a52d5af chore: alpha ci 2022-12-13 17:44:46 +08:00
GyDi e5109789bf chore: alpha ci 2022-12-13 17:41:53 +08:00
GyDi 4d2b35e09d feat: support to change proxy layout column 2022-12-13 17:34:39 +08:00
GyDi 5c5177ec57 feat: support to open core dir 2022-12-13 00:44:24 +08:00
GyDi d93b00cd15 chore: update deps 2022-12-12 00:00:45 +08:00
MoeShin 39ade59174 fix: Virtuoso no work in legacy browsers (#318) 2022-12-08 10:47:42 +08:00
GyDi 0309c815b9 chore: update deps 2022-12-04 20:54:43 +08:00
GyDi ce613098db fix: adjust ui 2022-12-04 00:45:39 +08:00
GyDi ab34044196 feat: profile page ui 2022-11-28 22:29:58 +08:00
GyDi 17f724748f chore: deps 2022-11-28 22:27:23 +08:00
GyDi f3a917b5e7 chore: update clash version 2022-11-26 13:25:52 +08:00
GyDi 376011ea08 fix: refresh websocket 2022-11-25 22:22:57 +08:00
GyDi 2ce7624c14 fix: adjust ui 2022-11-24 18:24:34 +08:00
GyDi c62dddd5b9 feat: save some fields in the runtime config, close #292 2022-11-24 17:52:25 +08:00
GyDi 490ba9f140 chore: alpha ci 2022-11-24 11:14:03 +08:00
GyDi f76890cc56 fix: parse bytes base 1024 2022-11-24 11:11:31 +08:00
GyDi e1c8f1fed9 fix: add clash fields 2022-11-24 10:46:21 +08:00
GyDi 6e19a4ab8b chore: ci 2022-11-24 10:41:03 +08:00
GyDi b156523a7f chore: ci 2022-11-24 10:35:22 +08:00
GyDi d20b745ae5 fix: direct mode hide proxies 2022-11-24 10:35:09 +08:00
GyDi c51e9e6b2c feat: add meta feature 2022-11-24 10:26:41 +08:00
GyDi 1a31fa9067 fix: profile can not edit 2022-11-24 10:26:25 +08:00
GyDi 558b8499af chore: ci 2022-11-23 23:46:32 +08:00
200 changed files with 20318 additions and 7445 deletions
+56
View File
@@ -0,0 +1,56 @@
name: 问题反馈 / Bug report
title: "[BUG] "
description: 反馈你遇到的问题 / Report the issue you are experiencing
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
## 在提交问题之前,请确认以下事项:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/install/)
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) and [FAQ](https://clash-verge-rev.github.io/faq/install/)
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the problem still exists
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
- type: textarea
id: description
attributes:
label: 问题描述 / Describe the bug
description: 详细清晰地描述你遇到的问题 / A clear and concise description of what the bug is
validations:
required: true
- type: textarea
attributes:
label: 复现步骤 / To Reproduce
description: 请提供复现问题的步骤 / Steps to reproduce the behavior
validations:
required: true
- type: dropdown
attributes:
label: 操作系统 / OS
options:
- Windows
- Linux
- MacOS
validations:
required: true
- type: input
attributes:
label: 操作系统版本 / OS Version
description: 请提供你的操作系统版本,Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
validations:
required: true
- type: textarea
attributes:
label: 日志 / Log
description: 请提供完整或相关部分的Debug日志 / Please provide the complete or relevant part of the Debug log
validations:
required: true
+4
View File
@@ -0,0 +1,4 @@
contact_links:
- name: 讨论交流 / Communication
url: https://t.me/clash_verge_rev
about: 在 Telegram 群组中与其他用户讨论交流 / Communicate with other users in the Telegram group
@@ -0,0 +1,35 @@
name: 功能请求 / Feature request
title: "[Feature] "
description: 提出你的功能请求 / Propose your feature request
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
## 在提交问题之前,请确认以下事项:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 确认软件不存在类似的功能
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide.html) to confirm that the software does not have similar functions
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
- type: textarea
id: description
attributes:
label: 功能描述 / Feature description
description: 详细清晰地描述你的功能请求 / A clear and concise description of what the feature is
validations:
required: true
- type: textarea
attributes:
label: 使用场景 / Use case
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
validations:
required: true
+4
View File
@@ -0,0 +1,4 @@
FROM rust:buster
COPY entrypoint.sh /entrypoint.sh
RUN chmod a+x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+14
View File
@@ -0,0 +1,14 @@
name: "Build for Linux"
branding:
icon: user-check
color: gray-dark
inputs:
target:
required: true
description: "Rust Target"
runs:
using: "docker"
image: "Dockerfile"
args:
- ${{ inputs.target }}
+8
View File
@@ -0,0 +1,8 @@
pnpm install
pnpm check $INPUT_TARGET
sed -i "s/#openssl/openssl={version=\"0.10\",features=[\"vendored\"]}/g" src-tauri/Cargo.toml
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
pnpm build --target $INPUT_TARGET
else
pnpm build --target $INPUT_TARGET -b deb
fi
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
wget https://nodejs.org/dist/v20.10.0/node-v20.10.0-linux-x64.tar.xz
tar -Jxvf ./node-v20.10.0-linux-x64.tar.xz
export PATH=$(pwd)/node-v20.10.0-linux-x64/bin:$PATH
npm install pnpm -g
rustup target add "$INPUT_TARGET"
if [ "$INPUT_TARGET" = "x86_64-unknown-linux-gnu" ]; then
apt-get update
apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev patchelf
elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
dpkg --add-architecture arm64
apt-get update
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_ALLOW_CROSS=1
elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
dpkg --add-architecture armhf
apt-get update
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
export PKG_CONFIG_ALLOW_CROSS=1
elif [ "$INPUT_TARGET" = "riscv64gc-unknown-linux-gnu" ]; then
dpkg --add-architecture riscv64
apt-get update
apt-get install -y libncurses6:riscv64 libtinfo6:riscv64 linux-libc-dev:riscv64 libncursesw6:riscv64 libssl3:riscv64 libcups2:riscv64
apt-get install -y --no-install-recommends g++-riscv64-linux-gnu libc6-dev-riscv64-cross libwebkit2gtk-4.0-dev:riscv64 libgtk-3-dev:riscv64 patchelf:riscv64 librsvg2-dev:riscv64 libayatana-appindicator3-dev:riscv64
export CARGO_TARGET_RISCV64_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
export CC_riscv64_unknown_linux_gnu=riscv64-linux-gnu-gcc
export CXX_riscv64_unknown_linux_gnu=riscv64-linux-gnu-g++
export PKG_CONFIG_PATH=/usr/lib/riscv64-linux-gnu/pkgconfig
export PKG_CONFIG_ALLOW_CROSS=1
else
echo "Unknown target: $INPUT_TARGET" && exit 1
fi
bash .github/build-for-linux/build.sh
+165 -47
View File
@@ -1,34 +1,40 @@
name: Alpha CI
name: Alpha Build
on:
workflow_dispatch:
inputs:
debug:
type: boolean
default: false
push:
branches: [main]
tags-ignore: [updater, alpha]
permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
jobs:
release:
alpha:
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
if: startsWith(github.repository, 'zzzgydi')
steps:
- name: Checkout repository
uses: actions/checkout@v2
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -36,33 +42,20 @@ jobs:
workspaces: src-tauri
- name: Install Node
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: 16
node-version: "20"
- name: Install Dependencies (ubuntu only)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
- name: Get yarn cache dir path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn Cache
uses: actions/cache@v2
id: yarn-cache
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
version: 9
run_install: false
- name: Yarn install and check
- name: Pnpm install and check
run: |
yarn install --network-timeout 1000000
yarn run check
pnpm i
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
@@ -72,16 +65,141 @@ jobs:
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tagName: alpha
releaseName: "Clash Verge Alpha"
releaseBody: "Alpha Version (include debug)"
releaseName: "Clash Verge Rev Alpha"
releaseBody: "More new features are now supported."
releaseDraft: false
includeDebug: ${{ github.event.inputs.debug }}
prerelease: true
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Portable Bundle
if: matrix.os == 'windows-latest'
run: |
yarn run portable
run: pnpm portable ${{ matrix.target }} --alpha
env:
TAG_NAME: alpha
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
alpha-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
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
if: startsWith(matrix.target, 'x86_64')
uses: softprops/action-gh-release@v1
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
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: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: [alpha, alpha-for-linux]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Update Tag
uses: richardsimko/update-tag@v1
with:
tag_name: alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
### 我应该下载哪个版本?
- 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
uses: softprops/action-gh-release@v1
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
-122
View File
@@ -1,122 +0,0 @@
name: Release CI
on:
push:
tags:
- v**
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
jobs:
release:
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
if: |
startsWith(github.repository, 'zzzgydi') &&
startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install Dependencies (ubuntu only)
if: startsWith(matrix.os, 'ubuntu-')
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
- name: Get yarn cache dir path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn Cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install and check
run: |
yarn install --network-timeout 1000000
yarn run check
- name: Tauri build
uses: tauri-apps/tauri-action@v0
# enable cache even though failed
# continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tagName: v__VERSION__
releaseName: "Clash Verge v__VERSION__"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
- name: Portable Bundle
if: matrix.os == 'windows-latest'
# rebuild with env settings
run: |
yarn build
yarn run portable
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
VITE_WIN_PORTABLE: 1
release-update:
needs: release
runs-on: macos-latest
if: |
startsWith(github.repository, 'zzzgydi') &&
startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Get yarn cache dir path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn Cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Release updater file
run: yarn run updater
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-102
View File
@@ -1,102 +0,0 @@
name: Compatible CI
on:
workflow_dispatch:
push:
tags:
- v**
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
jobs:
build:
strategy:
fail-fast: false
matrix:
targets:
- tag: macOS-10.15
os: macos-10.15
- tag: Ubuntu18
os: ubuntu-18.04
- tag: Ubuntu22
os: ubuntu-22.04
runs-on: ${{ matrix.targets.os }}
if: startsWith(github.repository, 'zzzgydi')
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 16
# - name: Install Dependencies (ubuntu18 only)
# if: matrix.targets.os == 'ubuntu-18.04'
# run: |
# sudo apt-get update
# sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev libayatana-appindicator3-dev
- name: Install Dependencies (ubuntu22 only)
if: startsWith(matrix.targets.os, 'ubuntu-')
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
- name: Get yarn cache dir path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn Cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install and check
run: |
yarn install --network-timeout 1000000
yarn run check
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tagName: ${{ matrix.targets.tag }}
releaseName: "Compatible For ${{ matrix.targets.tag }}"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: false
# - name: Portable Bundle
# if: matrix.os == 'windows-latest'
# # rebuild with env settings
# run: |
# yarn build
# yarn run portable
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
# TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# VITE_WIN_PORTABLE: 1
+153
View File
@@ -0,0 +1,153 @@
name: Release Build
on:
workflow_dispatch:
permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
jobs:
release:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "20"
- uses: pnpm/action-setup@v3
name: Install pnpm
with:
version: 9
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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tagName: v__VERSION__
releaseName: "Clash Verge Rev v__VERSION__"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: false
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Portable Bundle
if: matrix.os == 'windows-latest'
run: pnpm portable ${{ matrix.target }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
release-for-linux:
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:
runs-on: ubuntu-latest
needs: [release, release-for-linux]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "20"
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Pnpm install
run: pnpm i
- name: Release updater file
run: pnpm updater
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-86
View File
@@ -1,86 +0,0 @@
name: Test CI
on:
workflow_dispatch:
inputs:
os:
description: "Runs on OS"
required: true
default: windows-latest
type: choice
options:
- windows-latest
- ubuntu-latest
- macos-latest
- ubuntu-18.04
- ubuntu-20.04
- ubuntu-22.04
- macos-10.15
- macos-11
- macos-12
- windows-2019
- windows-2022
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
jobs:
release:
runs-on: ${{ github.event.inputs.os }}
if: startsWith(github.repository, 'zzzgydi')
steps:
- name: System Version
run: |
echo ${{ github.event.inputs.os }}
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install Dependencies (ubuntu only)
if: startsWith(github.event.inputs.os, 'ubuntu-')
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
- name: Get yarn cache dir path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn Cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install and check
run: |
yarn install --network-timeout 1000000
yarn run check
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
+15 -18
View File
@@ -1,32 +1,29 @@
name: Updater CI
on: workflow_dispatch
permissions: write-all
jobs:
release-update:
runs-on: macos-latest
if: startsWith(github.repository, 'zzzgydi')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Get yarn cache dir path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn Cache
uses: actions/cache@v2
id: yarn-cache
- name: Install Node
uses: actions/setup-node@v3
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
node-version: "20"
- name: Yarn install
run: yarn install
- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false
- name: Pnpm install
run: pnpm i
- name: Release updater file
run: yarn run updater
run: pnpm updater
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2
View File
@@ -1,4 +1,5 @@
node_modules
.pnpm-store
.DS_Store
dist
dist-ssr
@@ -6,3 +7,4 @@ dist-ssr
update.json
scripts/_env.sh
.vscode
.tool-versions
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn pretty-quick --staged
pnpm pretty-quick --staged
+1
View File
@@ -0,0 +1 @@
nodejs 21.7.1
+67
View File
@@ -0,0 +1,67 @@
# CONTRIBUTING
Thank you for your interest in contributing to Clash Verge Rev! This document provides guidelines and instructions to help you set up your development environment and start contributing.
## Development Setup
Before you start contributing to the project, you need to set up your development environment. Here are the steps you need to follow:
### Prerequisites
1. **Install Rust and Node.js**: Our project requires both Rust and Node.js. Please follow the instructions provided [here](https://tauri.app/v1/guides/getting-started/prerequisites) to install them on your system.
### Setup for Windows Users
If you're a Windows user, you may need to perform some additional steps:
- Make sure to add Rust and Node.js to your system's PATH. This is usually done during the installation process, but you can verify and manually add them if necessary.
- The gnu `patch` tool should be installed
### Install Node.js Packages
After installing Rust and Node.js, install the necessary Node.js packages:
```shell
pnpm i
```
### Download the Clash Binary
You have two options for downloading the clash binary:
- Automatically download it via the provided script:
```shell
pnpm run check
# Use '--force' to force update to the latest version
# pnpm run check --force
```
- Manually download it from the [Clash Meta release](https://github.com/MetaCubeX/Clash.Meta/releases). After downloading, rename the binary according to the [Tauri configuration](https://tauri.app/v1/api/config#bundleconfig.externalbin).
### Run the Development Server
To run the development server, use the following command:
```shell
pnpm dev
# If an app instance already exists, use a different command
pnpm dev:diff
```
### Build the Project
If you want to build the project, use:
```shell
pnpm build
```
## Contributing Your Changes
Once you have made your changes:
1. Fork the repository.
2. Create a new branch for your feature or bug fix.
3. Commit your changes with clear and concise commit messages.
4. Push your branch to your fork and submit a pull request to our repository.
We appreciate your contributions and look forward to your active participation in our project!
+46 -63
View File
@@ -1,97 +1,80 @@
<h1 align="center">
<img src="./src/assets/image/logo.png" alt="Clash" width="128" />
<br>
Clash Verge
Continuation of <a href="https://github.com/zzzgydi/clash-verge">Clash Verge</a>
<br>
</h1>
<h3 align="center">
A <a href="https://github.com/Dreamacro/clash">Clash</a> GUI based on <a href="https://github.com/tauri-apps/tauri">tauri</a>.
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
</h3>
## Features
## Preview
- Full `clash` config supported, Partial `clash premium` config supported.
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://github.com/zzzgydi/clash-verge/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- Simple UI and supports custom theme color.
- Built-in support [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) core.
- System proxy setting and guard.
![preview](./docs/preview.png)
## Install
Download from [release](https://github.com/zzzgydi/clash-verge/releases). Supports Windows x64, Linux x86_64 and macOS 11+
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
Go to the [release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
### 安装说明和常见问题,请到[文档页](https://clash-verge-rev.github.io/)查看:[Doc](https://clash-verge-rev.github.io/)
Notes: If you could not start the app on Windows, please check that you have [Webview2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) installed.
---
### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
## Promotion
[狗狗加速 —— 技术流机场 Doggygo VPN](https://狗狗加速.com)
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:https://verge.狗狗加速.com/#/register?code=oaxsAGo6
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
- 海外团队,无跑路风险,高达 50% 返佣
- 集群负载均衡设计,高速专线(兼容老客户端),极低延迟,无视晚高峰,4K 秒开
- 全球首家 Hysteria 协议机场,现已上线更快的 `Hysteria2` 协议(Clash Verge 客户端最佳搭配)
- 解锁流媒体及 ChatGPT
- 官网:https://狗狗加速.com
## Features
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
- Improved UI and supports custom theme color.
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
- System proxy setting and guard.
### FAQ
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/install/)
## Development
You should install Rust and Nodejs, see [here](https://tauri.studio/docs/getting-started/prerequisites) for more details. Then install Nodejs packages.
See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
To run the development server, execute the following commands after all prerequisites for **Tauri** are installed:
```shell
yarn install
pnpm i
pnpm run check
pnpm dev
```
Then download the clash binary... Or you can download it from [clash premium release](https://github.com/Dreamacro/clash/releases/tag/premium) and rename it according to [tauri config](https://tauri.studio/docs/api/config/#tauri.bundle.externalBin).
```shell
yarn run check
```
Then run
```shell
yarn dev
```
Or you can build it
```shell
yarn build
```
## Todos
> This keng is a little big...
## Screenshots
<div align="center">
<img src="./docs/demo1.png" alt="demo1" width="32%" />
<img src="./docs/demo2.png" alt="demo2" width="32%" />
<img src="./docs/demo3.png" alt="demo3" width="32%" />
<img src="./docs/demo4.png" alt="demo4" width="32%" />
<img src="./docs/demo5.png" alt="demo5" width="32%" />
<img src="./docs/demo6.png" alt="demo6" width="32%" />
</div>
### Custom Theme
<div align="center">
<img src="./docs/color1.png" alt="demo1" width="16%" />
<img src="./docs/color2.png" alt="demo2" width="16%" />
<img src="./docs/color3.png" alt="demo3" width="16%" />
<img src="./docs/color4.png" alt="demo4" width="16%" />
<img src="./docs/color5.png" alt="demo5" width="16%" />
<img src="./docs/color6.png" alt="demo6" width="16%" />
</div>
## Disclaimer
This is a learning project for Rust practice.
## Contributions
Issue and PR welcome!
## Acknowledgement
Clash Verge was based on or inspired by these projects and so on:
Clash Verge rev was based on or inspired by these projects and so on:
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): A Clash GUI based on tauri. Supports Windows, macOS and Linux.
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop applications with a web frontend.
- [Dreamacro/clash](https://github.com/Dreamacro/clash): A rule-based tunnel in Go.
- [MetaCubeX/Clash.Meta](https://github.com/MetaCubeX/Clash.Meta): A rule-based tunnel in Go.
- [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo): A rule-based tunnel in Go.
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): A Windows/macOS GUI based on Clash.
- [vitejs/vite](https://github.com/vitejs/vite): Next generation frontend tooling. It's fast!
+613
View File
@@ -1,3 +1,616 @@
## v1.6.1
### Features
- 鼠标悬浮显示当前订阅的名称 [#938](https://github.com/clash-verge-rev/clash-verge-rev/pull/938)
- 日志过滤支持正则表达式 [#959](https://github.com/clash-verge-rev/clash-verge-rev/pull/959)
- 更新 Clash 内核到 1.18.4
### Bugs Fixes
- 修复 Linux KDE 环境下系统代理无法开启的问题
- 窗口最大化图标调整 [#924](https://github.com/clash-verge-rev/clash-verge-rev/pull/924)
- 修改 MacOS 托盘点击行为(左键菜单,右键点击事件)
- 修复 MacOS 服务模式安装失败的问题
---
## v1.6.0
### Features
- Meta(mihomo)内核回退 1.18.1(当前新版内核 hy2 协议有 bug,等修复后更新)
- 多处界面细节调整 [#724](https://github.com/clash-verge-rev/clash-verge-rev/pull/724) [#799](https://github.com/clash-verge-rev/clash-verge-rev/pull/799) [#900](https://github.com/clash-verge-rev/clash-verge-rev/pull/900) [#901](https://github.com/clash-verge-rev/clash-verge-rev/pull/901)
- Linux 下新增服务模式
- 新增订阅卡片右键可以打开机场首页
- url-test 支持手动选择、节点组 fixed 节点使用角标展示 [#840](https://github.com/clash-verge-rev/clash-verge-rev/pull/840)
- Clash 配置、Merge 配置提供 JSON Schema 语法支持、连接界面调整 [#887](https://github.com/clash-verge-rev/clash-verge-rev/pull/887)
- 修改 Merge 配置文件默认内容 [#889](https://github.com/clash-verge-rev/clash-verge-rev/pull/889)
- 修改 tun 模式默认 mtu 为 1500,老版本升级,需在 tun 模式设置下“重置为默认值”。
- 使用 npm 安装 meta-json-schema [#895](https://github.com/clash-verge-rev/clash-verge-rev/pull/895)
- 更新部分翻译 [#904](https://github.com/clash-verge-rev/clash-verge-rev/pull/904)
- 支持 ico 格式的任务栏图标
### Bugs Fixes
- 修复 Linux KDE 环境下系统代理无法开启的问题
- 修复延迟检测动画问题
- 窗口最大化图标调整 [#816](https://github.com/clash-verge-rev/clash-verge-rev/pull/816)
- 修复 Windows 某些情况下无法安装服务模式 [#822](https://github.com/clash-verge-rev/clash-verge-rev/pull/822)
- UI 细节修复 [#821](https://github.com/clash-verge-rev/clash-verge-rev/pull/821)
- 修复使用默认编辑器打开配置文件
- 修复内核文件在特定目录也可以更新的问题 [#857](https://github.com/clash-verge-rev/clash-verge-rev/pull/857)
- 修复服务模式的安装目录问题
- 修复删除配置文件的“更新间隔”出现的问题 [#907](https://github.com/clash-verge-rev/clash-verge-rev/issues/907)
### 已知问题(历史遗留问题,暂未找到有效解决方案)
- MacOS M 芯片下服务模式无法安装;临时解决方案:在内核 ⚙️ 下,手动授权,再打开 tun 模式。
- MacOS 下如果删除过网络配置,会导致无法正常打开系统代理;临时解决方案:使用浏览器代理插件或手动配置系统代理。
- Window 拨号连接下无法正确识别并打开系统代理;临时解决方案:使用浏览器代理插件或使用 tun 模式。
---
## v1.5.11
### Features
- Meta(mihomo)内核更新 1.18.2
### Bugs Fixes
- 升级图标无法点击的问题
- 卸载时检查安装目录是否为空
- 代理界面图标重合的问题
---
## v1.5.10
### Features
- 优化 Linux 托盘菜单显示
- 添加透明代理端口设置
- 删除订阅前确认
### Bugs Fixes
- 删除 MacOS 程序坞图标
- Windows 下 service 日志没有清理
- MacOS 无法开启系统代理
---
## v1.5.9
### Features
- 缓存代理组图标
- 使用`boa_engine` 代替 `rquickjs`
- 支持 Linux armv7
### Bugs Fixes
- Windows 首次安装无法点击
- Windows 触摸屏无法拖动
- 规则列表 `REJECT-DROP` 颜色
- MacOS Dock 栏不显示图标
- MacOS 自定义字体无效
- 避免使用空 UA 拉取订阅
---
## v1.5.8
### Features
- 优化 UI 细节
- Linux 绘制窗口圆角
- 开放 DevTools
### Bugs Fixes
- 修复 MacOS 下开启 Tun 内核崩溃的问题
---
## v1.5.7
### Features
- 优化 UI 各种细节
- 提供菜单栏图标样式切换选项(单色/彩色/禁用)
- 添加自动检查更新开关
- MacOS 开启 Tun 模式自动修改 DNS
- 调整可拖动区域(尝试修复触摸屏无法拖动的问题)
---
## v1.5.6
### Features
- 全新专属 Verge rev UI 界面 (by @Amnesiash) 及细节调整
- 提供允许无效证书的开关
- 删除不必要的快捷键
- Provider 更新添加动画
- Merge 支持 Provider
- 更换订阅框的粘贴按钮,删除默认的"Remote File" Profile 名称
- 链接菜单添加节点显示
### Bugs Fixes
- Linux 下图片显示错误
---
## v1.5.4
### Features
- 支持自定义托盘图标
- 支持禁用代理组图标
- 代理组显示当前代理
- 修改 `打开面板` 快捷键为`打开/关闭面板`
---
## v1.5.3
### Features
- Tun 设置添加重置按钮
### Bugs Fixes
- Tun 设置项显示错误的问题
- 修改一些默认值
- 启动时不更改启动项设置
---
## v1.5.2
### Features
- 支持自定义延迟测试超时时间
- 优化 Tun 相关设置
### Bugs Fixes
- Merge 操作出错
- 安装后重启服务
- 修复管理员权限启动时开机启动失效的问题
---
## v1.5.1
### Features
- 保存窗口最大化状态
- Proxy Provider 显示数量
- 不再提供 32 位安装包(因为 32 位经常出现各种奇怪问题,比如 tun 模式无法开启;现在系统也几乎没有 32 位了)
### Bugs Fixes
- 优化设置项名称
- 自定义 GLOBAL 代理组时代理组显示错误的问题
---
## v1.5.0
### Features
- 删除 Clash 字段过滤功能
- 添加 socks 端口和 http 端口设置
- 升级内核到 1.18.1
### Bugs Fixes
- 修复 32 位版本无法显示流量信息的问题
---
## v1.4.11
### Break Changes
- 此版本更改了 Windows 安装包安装模式,需要卸载后手动安装,否则无法安装到正确位置
### Features
- 优化了系统代理开启的代码,解决了稀有场景下代理开启卡顿的问题
- 添加 MacOS 下的 debug 日志,以便日后调试稀有场景下 MacOS 下无法开启系统代理的问题
- MacOS 关闭 GUI 时同步杀除后台 GUI [#306](https://github.com/clash-verge-rev/clash-verge-rev/issues/306)
### Bugs Fixes
- 解决自动更新时文件占用问题
- 解决稀有场景下系统代理开启失败的问题
- 删除冗余内核代码
---
## v1.4.10
### Features
- 设置中添加退出按钮
- 支持自定义软件启动页
- 在 Proxy Provider 页面展示订阅信息
- 优化 Provider 支持
### Bugs Fixes
- 更改端口时立即重设系统代理
- 网站测试超时错误
---
## v1.4.9
### Features
- 支持启动时运行脚本
- 支持代理组显示图标
- 新增测试页面
### Bugs Fixes
- 连接页面时间排序错误
- 连接页面表格宽度优化
---
## v1.4.8
### Features
- 连接页面总流量显示
### Bugs Fixes
- 连接页面数据排序错误
- 新建订阅时设置更新间隔无效
- Windows 拨号网络无法设置系统代理
- Windows 开启/关闭系统代理延迟(使用注册表即可)
- 删除无效的背景模糊选项
---
## v1.4.7
### Features
- Windows 便携版禁用应用内更新
- 支持代理组 Hidden 选项
- 支持 URL Scheme(MacOS & Linux)
---
## v1.4.6
### Features
- 更新 Clash Meta(mihomo) 内核到 v1.18.0
- 支持 URL Scheme(暂时仅支持 Windows)
- 添加窗口置顶按钮
- UI 优化调整
### Bugs Fixes
- 修复一些编译错误
- 获取订阅名称错误
- 订阅信息解析错误
---
## v1.4.5
### Features
- 更新 MacOS 托盘图标样式(@gxx2778 贡献)
### Bugs Fixes
- Windows 下更新时无法覆盖`clash-verge-service.exe`的问题(需要卸载重装一次服务,下次更新生效)
- 窗口最大化按钮变化问题
- 窗口尺寸保存错误问题
- 复制环境变量类型无法切换问题
- 某些情况下闪退的问题
- 某些订阅无法导入的问题
---
## v1.4.4
### Features
- 支持 Windows aarch64(arm64) 版本
- 支持一键更新 GeoData
- 支持一键更新 Alpha 内核
- MacOS 支持在系统代理时显示不同的托盘图标
- Linux 支持在系统代理时显示不同的托盘图标
- 优化复制环境变量逻辑
### Bugs Fixes
- 修改 PID 文件的路径
### Performance
- 优化创建窗口的速度
---
## v1.4.3
### Break Changes
- 更改配置文件路径到标准目录(可以保证卸载时没有残留)
- 更改 appid 为 `io.github.clash-verge-rev.clash-verge-rev`
- 建议卸载旧版本后再安装新版本,该版本安装后不会使用旧版配置文件,你可以手动将旧版配置文件迁移到新版配置文件目录下
### Features
- 移除页面切换动画
- 更改 Tun 模式托盘图标颜色
- Portable 版本默认使用当前目录作为配置文件目录
- 禁用 Clash 字段过滤时隐藏 Clash 字段选项
- 优化拖拽时光标样式
### Bugs Fixes
- 修复 windows 下更新时没有关闭内核导致的更新失败的问题
- 修复打开文件报错的问题
- 修复 url 导入时无法获取中文配置名称的问题
- 修复 alpha 内核无法显示内存信息的问题
---
## v1.4.2
### Features
- update clash meta core to mihomo 1.17.0
- support both clash meta stable release and prerelease-alpha release
- fixed the problem of not being able to set the system proxy when there is a dial-up link on windows system [#833](https://github.com/zzzgydi/clash-verge/issues/833)
- support new clash field
- support random mixed port
- add windows x86 and linux armv7 support
- support disable tray click event
- add download progress for updater
- support drag to reorder the profile
- embed emoji fonts
- update depends
- improve UI style
---
## v1.4.1
### Features
- update clash meta core to newest 虚空终端(2023.11.23)
- delete clash core UI
- improve UI
- change Logo to original
---
## v1.4.0
### Features
- update clash meta core to newest 虚空终端
- delete clash core, no longer maintain
- merge Clash nyanpasu changes
- remove delay display different color
- use Meta Country.mmdb
- update dependencies
- small changes here and there
---
## v1.3.8
### Features
- update clash meta core
- add default valid keys
- adjust the delay display interval and color
### Bug Fixes
- fix connections page undefined exception
---
## v1.3.7
### Features
- update clash and clash meta core
- profiles page add paste button
- subscriptions url textfield use multi lines
- set min window size
- add check for updates buttons
- add open dashboard to the hotkey list
### Bug Fixes
- fix profiles page undefined exception
---
## v1.3.6
### Features
- add russian translation
- support to show connection detail
- support clash meta memory usage display
- support proxy provider update ui
- update geo data file from meta repo
- adjust setting page
### Bug Fixes
- center the window when it is out of screen
- use `sudo` when `pkexec` not found (Linux)
- reconnect websocket when window focus
### Notes
- The current version of the Linux installation package is built by Ubuntu 20.04 (Github Action).
---
## v1.3.5
### Features
- update clash core
### Bug Fixes
- fix blurry system tray icon (Windows)
- fix v1.3.4 wintun.dll not found (Windows)
- fix v1.3.4 clash core not found (macOS, Linux)
---
## v1.3.4
### Features
- update clash and clash meta core
- optimize traffic graph high CPU usage when window hidden
- use polkit to elevate permission (Linux)
- support app log level setting
- support copy environment variable
- overwrite resource file according to file modified
- save window size and position
### Bug Fixes
- remove fallback group select status
- enable context menu on editable element (Windows)
---
## v1.3.3
### Features
- update clash and clash meta core
- show tray icon variants in different system proxy status (Windows)
- close all connections when mode changed
### Bug Fixes
- encode controller secret into uri
- error boundary for each page
---
## v1.3.2
### Features
- update clash and clash meta core
### Bug Fixes
- fix import url issue
- fix profile undefined issue
---
## v1.3.1
### Features
- update clash and clash meta core
### Bug Fixes
- fix open url issue
- fix appimage path panic
- fix grant root permission in macOS
- fix linux system proxy default bypass
---
## v1.3.0
### Features
- update clash and clash meta
- support opening dir on tray
- support updating all profiles with one click
- support granting root permission to clash core(Linux, macOS)
- support enable/disable clash fields filter, feel free to experience the latest features of Clash Meta
### Bug Fixes
- deb add openssl depend(Linux)
- fix the AppImage auto launch path(Linux)
- fix get the default network service(macOS)
- remove the esc key listener in macOS, cmd+w instead(macOS)
- fix infinite retry when websocket error
---
## v1.2.3
### Features
- update clash
- adjust macOS window style
- profile supports UTF8 with BOM
### Bug Fixes
- fix selected proxy
- fix error log
---
## v1.2.2
### Features
- update clash meta
- recover clash core after panic
- use system window decorations(Linux)
### Bug Fixes
- flush system proxy settings(Windows)
- fix parse log panic
- fix ui bug
---
## v1.2.1
### Features
- update clash version
- proxy groups support multi columns
- optimize ui
### Bug Fixes
- fix ui websocket connection
- adjust delay check concurrency
- avoid setting login item repeatedly(macOS)
---
## v1.2.0
### Features
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

+51 -39
View File
@@ -1,7 +1,7 @@
{
"name": "clash-verge",
"version": "1.2.0",
"license": "GPL-3.0",
"version": "1.6.1",
"license": "GPL-3.0-only",
"scripts": {
"dev": "tauri dev",
"dev:diff": "tauri dev -f verge-dev",
@@ -10,57 +10,69 @@
"web:dev": "vite",
"web:build": "tsc && vite build",
"web:serve": "vite preview",
"aarch": "node scripts/aarch.mjs",
"check": "node scripts/check.mjs",
"updater": "node scripts/updater.mjs",
"publish": "node scripts/publish.mjs",
"portable": "node scripts/portable.mjs",
"prepare": "husky install"
},
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/icons-material": "^5.10.9",
"@mui/material": "^5.10.13",
"@mui/x-data-grid": "^5.17.11",
"@tauri-apps/api": "^1.2.0",
"ahooks": "^3.7.2",
"axios": "^1.1.3",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^5.15.15",
"@mui/lab": "5.0.0-alpha.149",
"@mui/material": "^5.15.15",
"@mui/x-data-grid": "^6.19.11",
"@tauri-apps/api": "^1.5.4",
"@types/json-schema": "^7.0.15",
"ahooks": "^3.7.11",
"axios": "^1.6.8",
"dayjs": "1.11.5",
"i18next": "^22.0.4",
"monaco-editor": "^0.34.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"i18next": "^23.11.2",
"lodash-es": "^4.17.21",
"meta-json-schema": "1.18.4-beta2",
"monaco-editor": "^0.47.0",
"monaco-yaml": "^5.1.1",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^3.1.4",
"react-i18next": "^12.0.0",
"react-router-dom": "^6.4.3",
"react-virtuoso": "^3.1.3",
"recoil": "^0.7.6",
"snarkdown": "^2.0.0",
"swr": "^1.3.0"
"react-hook-form": "^7.51.3",
"react-i18next": "^13.5.0",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.23.0",
"react-transition-group": "^4.4.5",
"react-virtuoso": "^4.7.10",
"recoil": "^0.7.7",
"swr": "^1.3.0",
"tar": "^6.2.1"
},
"devDependencies": {
"@actions/github": "^5.0.3",
"@tauri-apps/cli": "^1.2.0",
"@actions/github": "^5.1.1",
"@tauri-apps/cli": "^1.5.12",
"@types/fs-extra": "^9.0.13",
"@types/js-cookie": "^3.0.2",
"@types/lodash": "^4.14.180",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react": "^2.0.1",
"adm-zip": "^0.5.9",
"@types/js-cookie": "^3.0.6",
"@types/lodash-es": "^4.17.12",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@types/react-transition-group": "^4.4.10",
"@vitejs/plugin-react": "^4.2.1",
"adm-zip": "^0.5.12",
"cross-env": "^7.0.3",
"fs-extra": "^10.0.0",
"fs-extra": "^11.2.0",
"https-proxy-agent": "^5.0.1",
"husky": "^7.0.0",
"node-fetch": "^3.2.6",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"sass": "^1.54.0",
"typescript": "^4.7.4",
"vite": "^3.0.9",
"husky": "^7.0.4",
"node-fetch": "^3.3.2",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"sass": "^1.75.0",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svgr": "^2.2.1"
"vite-plugin-svgr": "^4.2.0"
},
"prettier": {
"tabWidth": 2,
+5602
View File
File diff suppressed because it is too large Load Diff
-104
View File
@@ -1,104 +0,0 @@
/**
* Build and upload assets for macOS(aarch)
*/
import fs from "fs-extra";
import path from "path";
import { exit } from "process";
import { createRequire } from "module";
import { getOctokit, context } from "@actions/github";
const require = createRequire(import.meta.url);
async function resolve() {
if (!process.env.GITHUB_TOKEN) {
throw new Error("GITHUB_TOKEN is required");
}
if (!process.env.GITHUB_REPOSITORY) {
throw new Error("GITHUB_REPOSITORY is required");
}
if (!process.env.TAURI_PRIVATE_KEY) {
throw new Error("TAURI_PRIVATE_KEY is required");
}
if (!process.env.TAURI_KEY_PASSWORD) {
throw new Error("TAURI_KEY_PASSWORD is required");
}
const { version } = require("../package.json");
const cwd = process.cwd();
const bundlePath = path.join(cwd, "src-tauri/target/release/bundle");
const join = (p) => path.join(bundlePath, p);
const appPathList = [
join("macos/Clash Verge.aarch64.app.tar.gz"),
join("macos/Clash Verge.aarch64.app.tar.gz.sig"),
];
for (const appPath of appPathList) {
if (fs.pathExistsSync(appPath)) {
fs.removeSync(appPath);
}
}
fs.copyFileSync(join("macos/Clash Verge.app.tar.gz"), appPathList[0]);
fs.copyFileSync(join("macos/Clash Verge.app.tar.gz.sig"), appPathList[1]);
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: `v${version}`,
});
if (!release.id) throw new Error("failed to find the release");
await uploadAssets(release.id, [
join(`dmg/Clash Verge_${version}_aarch64.dmg`),
...appPathList,
]);
}
// From tauri-apps/tauri-action
// https://github.com/tauri-apps/tauri-action/blob/dev/packages/action/src/upload-release-assets.ts
async function uploadAssets(releaseId, assets) {
const github = getOctokit(process.env.GITHUB_TOKEN);
// Determine content-length for header to upload asset
const contentLength = (filePath) => fs.statSync(filePath).size;
for (const assetPath of assets) {
const headers = {
"content-type": "application/zip",
"content-length": contentLength(assetPath),
};
const ext = path.extname(assetPath);
const filename = path.basename(assetPath).replace(ext, "");
const assetName = path.dirname(assetPath).includes(`target${path.sep}debug`)
? `${filename}-debug${ext}`
: `${filename}${ext}`;
console.log(`[INFO]: Uploading ${assetName}...`);
try {
await github.rest.repos.uploadReleaseAsset({
headers,
name: assetName,
data: fs.readFileSync(assetPath),
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
});
} catch (error) {
console.log(error.message);
}
}
}
if (process.platform === "darwin" && process.arch === "arm64") {
resolve();
} else {
console.error("invalid");
exit(1);
}
+330 -126
View File
@@ -1,5 +1,6 @@
import fs from "fs-extra";
import zlib from "zlib";
import tar from "tar";
import path from "path";
import AdmZip from "adm-zip";
import fetch from "node-fetch";
@@ -10,59 +11,169 @@ const cwd = process.cwd();
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
const FORCE = process.argv.includes("--force");
const SIDECAR_HOST = execSync("rustc -vV")
.toString()
.match(/(?<=host: ).+(?=\s*)/g)[0];
/* ======= clash ======= */
const CLASH_URL_PREFIX =
"https://github.com/Dreamacro/clash/releases/download/premium/";
const CLASH_LATEST_DATE = "2022.08.26";
const CLASH_MAP = {
"win32-x64": "clash-windows-amd64",
"darwin-x64": "clash-darwin-amd64",
"darwin-arm64": "clash-darwin-arm64",
"linux-x64": "clash-linux-amd64",
"linux-arm64": "clash-linux-armv8",
const PLATFORM_MAP = {
"x86_64-pc-windows-msvc": "win32",
"i686-pc-windows-msvc": "win32",
"aarch64-pc-windows-msvc": "win32",
"x86_64-apple-darwin": "darwin",
"aarch64-apple-darwin": "darwin",
"x86_64-unknown-linux-gnu": "linux",
"i686-unknown-linux-gnu": "linux",
"aarch64-unknown-linux-gnu": "linux",
"armv7-unknown-linux-gnueabihf": "linux",
"riscv64gc-unknown-linux-gnu": "linux",
"loongarch64-unknown-linux-gnu": "linux",
};
const ARCH_MAP = {
"x86_64-pc-windows-msvc": "x64",
"i686-pc-windows-msvc": "ia32",
"aarch64-pc-windows-msvc": "arm64",
"x86_64-apple-darwin": "x64",
"aarch64-apple-darwin": "arm64",
"x86_64-unknown-linux-gnu": "x64",
"i686-unknown-linux-gnu": "ia32",
"aarch64-unknown-linux-gnu": "arm64",
"armv7-unknown-linux-gnueabihf": "arm",
"riscv64gc-unknown-linux-gnu": "riscv64",
"loongarch64-unknown-linux-gnu": "loong64",
};
/* ======= clash meta ======= */
const META_URL_PREFIX = `https://github.com/MetaCubeX/Clash.Meta/releases/download/`;
const META_VERSION = "v1.13.2";
const arg1 = process.argv.slice(2)[0];
const arg2 = process.argv.slice(2)[1];
const target = arg1 === "--force" ? arg2 : arg1;
const { platform, arch } = target
? { platform: PLATFORM_MAP[target], arch: ARCH_MAP[target] }
: process;
const SIDECAR_HOST = target
? target
: execSync("rustc -vV")
.toString()
.match(/(?<=host: ).+(?=\s*)/g)[0];
/* ======= clash meta alpha======= */
const META_ALPHA_VERSION_URL =
"https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt";
const META_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha`;
let META_ALPHA_VERSION;
const META_ALPHA_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
"linux-arm64": "mihomo-linux-arm64",
"linux-arm": "mihomo-linux-armv7",
"linux-riscv64": "mihomo-linux-riscv64",
"linux-loong64": "mihomo-linux-loong64",
};
// Fetch the latest alpha release version from the version.txt file
async function getLatestAlphaVersion() {
const options = {};
const httpProxy =
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.HTTPS_PROXY ||
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
}
try {
const response = await fetch(META_ALPHA_VERSION_URL, {
...options,
method: "GET",
});
let v = await response.text();
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest alpha version: ${META_ALPHA_VERSION}`);
} catch (error) {
console.error("Error fetching latest alpha version:", error.message);
process.exit(1);
}
}
/* ======= clash meta stable ======= */
const META_VERSION_URL =
"https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt";
const META_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`;
let META_VERSION;
const META_MAP = {
"win32-x64": "Clash.Meta-windows-amd64-compatible",
"darwin-x64": "Clash.Meta-darwin-amd64",
"darwin-arm64": "Clash.Meta-darwin-arm64",
"linux-x64": "Clash.Meta-linux-amd64-compatible",
"linux-arm64": "Clash.Meta-linux-arm64",
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
"linux-arm64": "mihomo-linux-arm64",
"linux-arm": "mihomo-linux-armv7",
"linux-riscv64": "mihomo-linux-riscv64",
"linux-loong64": "mihomo-linux-loong64",
};
/**
// Fetch the latest release version from the version.txt file
async function getLatestReleaseVersion() {
const options = {};
const httpProxy =
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.HTTPS_PROXY ||
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
}
try {
const response = await fetch(META_VERSION_URL, {
...options,
method: "GET",
});
let v = await response.text();
META_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest release version: ${META_VERSION}`);
} catch (error) {
console.error("Error fetching latest release version:", error.message);
process.exit(1);
}
}
/*
* check available
*/
const { platform, arch } = process;
if (!CLASH_MAP[`${platform}-${arch}`]) {
throw new Error(`clash unsupport platform "${platform}-${arch}"`);
}
if (!META_MAP[`${platform}-${arch}`]) {
throw new Error(`clash meta unsupport platform "${platform}-${arch}"`);
throw new Error(
`clash meta alpha unsupported platform "${platform}-${arch}"`
);
}
function clash() {
const name = CLASH_MAP[`${platform}-${arch}`];
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
throw new Error(
`clash meta alpha unsupported platform "${platform}-${arch}"`
);
}
/**
* core info
*/
function clashMetaAlpha() {
const name = META_ALPHA_MAP[`${platform}-${arch}`];
const isWin = platform === "win32";
const urlExt = isWin ? "zip" : "gz";
const downloadURL = `${CLASH_URL_PREFIX}${name}-${CLASH_LATEST_DATE}.${urlExt}`;
const downloadURL = `${META_ALPHA_URL_PREFIX}/${name}-${META_ALPHA_VERSION}.${urlExt}`;
const exeFile = `${name}${isWin ? ".exe" : ""}`;
const zipFile = `${name}.${urlExt}`;
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
return {
name: "clash",
targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
name: "clash-meta-alpha",
targetFile: `clash-meta-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
exeFile,
zipFile,
downloadURL,
@@ -73,7 +184,7 @@ function clashMeta() {
const name = META_MAP[`${platform}-${arch}`];
const isWin = platform === "win32";
const urlExt = isWin ? "zip" : "gz";
const downloadURL = `${META_URL_PREFIX}${META_VERSION}/${name}-${META_VERSION}.${urlExt}`;
const downloadURL = `${META_URL_PREFIX}/${META_VERSION}/${name}-${META_VERSION}.${urlExt}`;
const exeFile = `${name}${isWin ? ".exe" : ""}`;
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
@@ -85,7 +196,6 @@ function clashMeta() {
downloadURL,
};
}
/**
* download sidecar and rename
*/
@@ -103,75 +213,68 @@ async function resolveSidecar(binInfo) {
const tempExe = path.join(tempDir, exeFile);
await fs.mkdirp(tempDir);
if (!(await fs.pathExists(tempZip))) await downloadFile(downloadURL, tempZip);
try {
if (!(await fs.pathExists(tempZip))) {
await downloadFile(downloadURL, tempZip);
}
if (zipFile.endsWith(".zip")) {
const zip = new AdmZip(tempZip);
zip.getEntries().forEach((entry) => {
console.log(`[DEBUG]: ${name} entry name`, entry.entryName);
});
zip.extractAllTo(tempDir, true);
await fs.rename(tempExe, sidecarPath);
console.log(`[INFO]: ${name} unzip finished`);
} else {
// gz
const readStream = fs.createReadStream(tempZip);
const writeStream = fs.createWriteStream(sidecarPath);
readStream
.pipe(zlib.createGunzip())
.pipe(writeStream)
.on("finish", () => {
console.log(`[INFO]: ${name} gunzip finished`);
execSync(`chmod 755 ${sidecarPath}`);
console.log(`[INFO]: ${name} chmod binary finished`);
})
.on("error", (error) => {
console.error(`[ERROR]: ${name} gz failed`, error.message);
throw error;
if (zipFile.endsWith(".zip")) {
const zip = new AdmZip(tempZip);
zip.getEntries().forEach((entry) => {
console.log(`[DEBUG]: "${name}" entry name`, entry.entryName);
});
zip.extractAllTo(tempDir, true);
await fs.rename(tempExe, sidecarPath);
console.log(`[INFO]: "${name}" unzip finished`);
} else if (zipFile.endsWith(".tgz")) {
// tgz
await fs.mkdirp(tempDir);
await tar.extract({
cwd: tempDir,
file: tempZip,
//strip: 1, // 可能需要根据实际的 .tgz 文件结构调整
});
const files = await fs.readdir(tempDir);
console.log(`[DEBUG]: "${name}" files in tempDir:`, files);
const extractedFile = files.find((file) => file.startsWith("虚空终端-"));
if (extractedFile) {
const extractedFilePath = path.join(tempDir, extractedFile);
await fs.rename(extractedFilePath, sidecarPath);
console.log(`[INFO]: "${name}" file renamed to "${sidecarPath}"`);
execSync(`chmod 755 ${sidecarPath}`);
console.log(`[INFO]: "${name}" chmod binary finished`);
} else {
throw new Error(`Expected file not found in ${tempDir}`);
}
} else {
// gz
const readStream = fs.createReadStream(tempZip);
const writeStream = fs.createWriteStream(sidecarPath);
await new Promise((resolve, reject) => {
const onError = (error) => {
console.error(`[ERROR]: "${name}" gz failed:`, error.message);
reject(error);
};
readStream
.pipe(zlib.createGunzip().on("error", onError))
.pipe(writeStream)
.on("finish", () => {
console.log(`[INFO]: "${name}" gunzip finished`);
execSync(`chmod 755 ${sidecarPath}`);
console.log(`[INFO]: "${name}" chmod binary finished`);
resolve();
})
.on("error", onError);
});
}
} catch (err) {
// 需要删除文件
await fs.remove(sidecarPath);
throw err;
} finally {
// delete temp dir
await fs.remove(tempDir);
}
// delete temp dir
await fs.remove(tempDir);
}
/**
* only Windows
* get the wintun.dll (not required)
*/
async function resolveWintun() {
const { platform } = process;
if (platform !== "win32") return;
const url = "https://www.wintun.net/builds/wintun-0.14.1.zip";
const tempDir = path.join(TEMP_DIR, "wintun");
const tempZip = path.join(tempDir, "wintun.zip");
const wintunPath = path.join(tempDir, "wintun/bin/amd64/wintun.dll");
const targetPath = path.join(cwd, "src-tauri/resources", "wintun.dll");
if (!FORCE && (await fs.pathExists(targetPath))) return;
await fs.mkdirp(tempDir);
if (!(await fs.pathExists(tempZip))) {
await downloadFile(url, tempZip);
}
// unzip
const zip = new AdmZip(tempZip);
zip.extractAllTo(tempDir, true);
if (!(await fs.pathExists(wintunPath))) {
throw new Error(`path not found "${wintunPath}"`);
}
await fs.rename(wintunPath, targetPath);
await fs.remove(tempDir);
console.log(`[INFO]: resolve wintun.dll finished`);
}
/**
@@ -218,66 +321,167 @@ async function downloadFile(url, path) {
console.log(`[INFO]: download finished "${url}"`);
}
// SimpleSC.dll
const resolvePlugin = async () => {
const url =
"https://nsis.sourceforge.io/mediawiki/images/e/ef/NSIS_Simple_Service_Plugin_Unicode_1.30.zip";
const tempDir = path.join(TEMP_DIR, "SimpleSC");
const tempZip = path.join(
tempDir,
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
);
const tempDll = path.join(tempDir, "SimpleSC.dll");
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
const pluginPath = path.join(pluginDir, "SimpleSC.dll");
await fs.mkdirp(pluginDir);
await fs.mkdirp(tempDir);
if (!FORCE && (await fs.pathExists(pluginPath))) return;
try {
if (!(await fs.pathExists(tempZip))) {
await downloadFile(url, tempZip);
}
const zip = new AdmZip(tempZip);
zip.getEntries().forEach((entry) => {
console.log(`[DEBUG]: "SimpleSC" entry name`, entry.entryName);
});
zip.extractAllTo(tempDir, true);
await fs.copyFile(tempDll, pluginPath);
console.log(`[INFO]: "SimpleSC" unzip finished`);
} finally {
await fs.remove(tempDir);
}
};
// service chmod
const resolveServicePermission = async () => {
const serviceExecutables = [
"clash-verge-service",
"install-service",
"uninstall-service",
];
const resDir = path.join(cwd, "src-tauri/resources");
for (let f of serviceExecutables) {
const targetPath = path.join(resDir, f);
if (await fs.pathExists(targetPath)) {
execSync(`chmod 755 ${targetPath}`);
console.log(`[INFO]: "${targetPath}" chmod finished`);
}
}
};
/**
* main
*/
const SERVICE_URL =
"https://github.com/zzzgydi/clash-verge-service/releases/download/latest";
const resolveService = () =>
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
const resolveService = () => {
let ext = platform === "win32" ? ".exe" : "";
resolveResource({
file: "clash-verge-service.exe",
downloadURL: `${SERVICE_URL}/clash-verge-service.exe`,
file: "clash-verge-service" + ext,
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
});
const resolveInstall = () =>
};
const resolveInstall = () => {
let ext = platform === "win32" ? ".exe" : "";
resolveResource({
file: "install-service.exe",
downloadURL: `${SERVICE_URL}/install-service.exe`,
file: "install-service" + ext,
downloadURL: `${SERVICE_URL}/install-service${ext}`,
});
const resolveUninstall = () =>
};
const resolveUninstall = () => {
let ext = platform === "win32" ? ".exe" : "";
resolveResource({
file: "uninstall-service.exe",
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
file: "uninstall-service" + ext,
downloadURL: `${SERVICE_URL}/uninstall-service${ext}`,
});
};
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 = () =>
resolveResource({
file: "Country.mmdb",
downloadURL: `https://github.com/Dreamacro/maxmind-geoip/releases/download/20221112/Country.mmdb`,
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb`,
});
const resolveGeosite = () =>
resolveResource({
file: "geosite.dat",
downloadURL: `https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat`,
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat`,
});
const resolveGeoIP = () =>
resolveResource({
file: "geoip.dat",
downloadURL: `https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat`,
downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat`,
});
const resolveEnableLoopback = () =>
resolveResource({
file: "enableLoopback.exe",
downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`,
});
const tasks = [
{ name: "clash", func: () => resolveSidecar(clash()), retry: 5 },
{ name: "clash-meta", func: () => resolveSidecar(clashMeta()), retry: 5 },
{ name: "wintun", func: resolveWintun, retry: 5, winOnly: true },
{ name: "service", func: resolveService, retry: 5, winOnly: true },
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
// { name: "clash", func: resolveClash, retry: 5 },
{
name: "clash-meta-alpha",
func: () =>
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
retry: 5,
},
{
name: "clash-meta",
func: () =>
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
retry: 5,
},
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
{ name: "service", func: resolveService, retry: 5 },
{ name: "install", func: resolveInstall, 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: "geosite", func: resolveGeosite, retry: 5 },
{ name: "geoip", func: resolveGeoIP, retry: 5 },
{
name: "enableLoopback",
func: resolveEnableLoopback,
retry: 5,
winOnly: true,
},
{
name: "service_chmod",
func: resolveServicePermission,
retry: 1,
unixOnly: true,
},
];
async function runTask() {
const task = tasks.shift();
if (!task) return;
if (task.winOnly && process.platform !== "win32") return runTask();
if (task.winOnly && platform !== "win32") return runTask();
if (task.linuxOnly && platform !== "linux") return runTask();
if (task.unixOnly && platform === "win32") return runTask();
for (let i = 0; i < task.retry; i++) {
try {
await task.func();
break;
} catch (err) {
console.error(`[ERROR]: task::${task.name} try ${i} == `, err.message);
console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message);
if (i === task.retry - 1) throw err;
}
}
return runTask();
+32 -6
View File
@@ -4,29 +4,44 @@ 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",
"aarch64-pc-windows-msvc": "arm64",
};
/// Script for ci
/// 打包绿色版/便携版 (only Windows)
async function resolvePortable() {
if (process.platform !== "win32") return;
const releaseDir = "./src-tauri/target/release";
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, "clash.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-meta.exe"));
zip.addLocalFile(path.join(releaseDir, "clash-meta-alpha.exe"));
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
zip.addLocalFolder(configDir, ".config");
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
const { version } = packageJson;
const zipFile = `Clash.Verge_${version}_x64_portable.zip`;
const zipFile = `Clash.Verge_${version}_${ARCH_MAP[target]}_portable.zip`;
zip.writeZip(zipFile);
console.log("[INFO]: create portable zip successfully");
@@ -38,14 +53,25 @@ async function resolvePortable() {
const options = { owner: context.repo.owner, repo: context.repo.repo };
const github = getOctokit(process.env.GITHUB_TOKEN);
console.log("[INFO]: upload to ", process.env.TAG_NAME || `v${version}`);
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: process.env.TAG_NAME || `v${version}`,
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({
-53
View File
@@ -1,53 +0,0 @@
import fs from "fs-extra";
import { createRequire } from "module";
import { execSync } from "child_process";
import { resolveUpdateLog } from "./updatelog.mjs";
const require = createRequire(import.meta.url);
// publish
async function resolvePublish() {
const flag = process.argv[2] ?? "patch";
const packageJson = require("../package.json");
const tauriJson = require("../src-tauri/tauri.conf.json");
let [a, b, c] = packageJson.version.split(".").map(Number);
if (flag === "major") {
a += 1;
b = 0;
c = 0;
} else if (flag === "minor") {
b += 1;
c = 0;
} else if (flag === "patch") {
c += 1;
} else throw new Error(`invalid flag "${flag}"`);
const nextVersion = `${a}.${b}.${c}`;
packageJson.version = nextVersion;
tauriJson.package.version = nextVersion;
// 发布更新前先写更新日志
const nextTag = `v${nextVersion}`;
await resolveUpdateLog(nextTag);
await fs.writeFile(
"./package.json",
JSON.stringify(packageJson, undefined, 2)
);
await fs.writeFile(
"./src-tauri/tauri.conf.json",
JSON.stringify(tauriJson, undefined, 2)
);
execSync("git add ./package.json");
execSync("git add ./src-tauri/tauri.conf.json");
execSync(`git commit -m "v${nextVersion}"`);
execSync(`git tag -a v${nextVersion} -m "v${nextVersion}"`);
execSync(`git push`);
execSync(`git push origin v${nextVersion}`);
console.log(`Publish Successfully...`);
}
resolvePublish();
+30 -11
View File
@@ -43,9 +43,12 @@ async function resolveUpdater() {
darwin: { signature: "", url: "" }, // compatible with older formats
"darwin-aarch64": { signature: "", url: "" },
"darwin-intel": { signature: "", url: "" },
"darwin-x86_64": { signature: "", url: "" },
"linux-x86_64": { signature: "", url: "" },
"linux-aarch64": { signature: "", url: "" },
"linux-armv7": { signature: "", url: "" },
"windows-x86_64": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" }, // no supported
"windows-aarch64": { signature: "", url: "" },
},
};
@@ -53,27 +56,39 @@ async function resolveUpdater() {
const { name, browser_download_url } = asset;
// win64 url
if (name.endsWith(".msi.zip") && name.includes("en-US")) {
if (name.endsWith("x64-setup.nsis.zip")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith(".msi.zip.sig") && name.includes("en-US")) {
if (name.endsWith("x64-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig;
}
// win arm url
if (name.endsWith("arm64-setup.nsis.zip")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64-setup.nsis.zip.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
// darwin url (intel)
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
updateData.platforms.darwin.url = browser_download_url;
updateData.platforms["darwin-intel"].url = browser_download_url;
updateData.platforms["darwin-x86_64"].url = browser_download_url;
}
// darwin signature (intel)
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.darwin.signature = sig;
updateData.platforms["darwin-intel"].signature = sig;
updateData.platforms["darwin-x86_64"].signature = sig;
}
// darwin url (aarch)
@@ -86,16 +101,22 @@ async function resolveUpdater() {
updateData.platforms["darwin-aarch64"].signature = sig;
}
// linux url
if (name.endsWith(".AppImage.tar.gz")) {
// 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 signature
if (name.endsWith(".AppImage.tar.gz.sig")) {
// linux x64 signature
if (name.endsWith("amd64.AppImage.tar.gz.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig;
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
updateData.platforms["linux-aarch64"].signature = sig;
updateData.platforms["linux-armv7"].signature = sig;
}
});
@@ -117,10 +138,8 @@ async function resolveUpdater() {
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
if (value.url) {
updateDataNew.platforms[key].url = value.url.replace(
"https://github.com/",
"https://hub.fastgit.xyz/"
);
updateDataNew.platforms[key].url =
"https://mirror.ghproxy.com/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
+3358 -1392
View File
File diff suppressed because it is too large Load Diff
+28 -29
View File
@@ -1,10 +1,10 @@
[package]
name = "clash-verge"
version = "0.1.0"
version = "1.6.1"
description = "clash verge"
authors = ["zzzgydi"]
license = "GPL-3.0"
repository = "https://github.com/zzzgydi/clash-verge.git"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
default-run = "clash-verge"
edition = "2021"
build = "build.rs"
@@ -14,45 +14,44 @@ tauri-build = { version = "1", features = [] }
[dependencies]
warp = "0.3"
which = "4.2.2"
anyhow = "1.0"
dirs = "4.0.0"
open = "3.0.3"
log = "0.4.14"
ctrlc = "3.2.3"
dunce = "1.0.2"
log4rs = "1.0.0"
nanoid = "0.4.0"
chrono = "0.4.19"
sysinfo = "0.26.2"
sysproxy = "0.1"
rquickjs = "0.1.7"
dirs = "5.0"
open = "5.0"
log = "0.4"
dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4"
sysinfo = "0.30"
boa_engine = "0.18"
serde_json = "1.0"
serde_yaml = "0.9"
auto-launch = "0.4"
once_cell = "1.14.0"
once_cell = "1.18"
port_scanner = "0.1.5"
delay_timer = "0.11.1"
parking_lot = "0.12.0"
delay_timer = "0.11.5"
parking_lot = "0.12"
percent-encoding = "2.3.1"
window-shadows = { version = "0.2" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.11", features = ["json"] }
tauri = { version = "1.1.1", features = ["global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
tauri-runtime-wry = { version = "0.12" }
window-vibrancy = { version = "0.3.0" }
window-shadows = { version = "0.2.0" }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
tauri = { version = "1.6", features = [ "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"] }
[target.'cfg(windows)'.dependencies]
runas = "0.2.1"
runas = "=1.2.0"
deelevate = "0.2.0"
winreg = { version = "0.10", features = ["transactions"] }
windows-sys = { version = "0.36", features = ["Win32_System_LibraryLoader", "Win32_System_SystemInformation"] }
winreg = "0.52.0"
[target.'cfg(target_os = "linux")'.dependencies]
users = "0.11.0"
#openssl
[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
verge-dev = []
debug-yml = []
[profile.release]
panic = "abort"
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Clash Verge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>clash</string>
</array>
</dict>
</array>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

+138 -20
View File
@@ -2,14 +2,14 @@ use crate::{
config::*,
core::*,
feat,
utils::{dirs, help},
utils::{dirs, help, resolve},
};
use crate::{ret_err, wrap_err};
use anyhow::{Context, Result};
use serde_yaml::Mapping;
use std::collections::{HashMap, VecDeque};
use sysproxy::Sysproxy;
use tauri::{api, Manager};
type CmdResult<T = ()> = Result<T, String>;
#[tauri::command]
@@ -30,6 +30,11 @@ pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult
wrap_err!(Config::profiles().data().append_item(item))
}
#[tauri::command]
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
}
#[tauri::command]
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
@@ -60,6 +65,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
match CoreManager::global().update_config().await {
Ok(_) => {
handle::Handle::refresh_clash();
let _ = handle::Handle::update_systray_part();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(())
@@ -80,7 +86,7 @@ pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
}
#[tauri::command]
pub fn view_profile(index: String) -> CmdResult {
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
let file = {
wrap_err!(Config::profiles().latest().get_item(&index))?
.file
@@ -93,7 +99,7 @@ pub fn view_profile(index: String) -> CmdResult {
ret_err!("the file not found");
}
wrap_err!(help::open_file(path))
wrap_err!(help::open_file(app_handle, path))
}
#[tauri::command]
@@ -175,6 +181,15 @@ pub async fn restart_sidecar() -> CmdResult {
wrap_err!(CoreManager::global().run_core().await)
}
#[tauri::command]
pub fn grant_permission(_core: String) -> CmdResult {
#[cfg(any(target_os = "macos", target_os = "linux"))]
return wrap_err!(manager::grant_permission(_core));
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
return Err("Unsupported target".into());
}
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> {
@@ -202,6 +217,13 @@ pub fn open_app_dir() -> CmdResult<()> {
wrap_err!(open::that(app_dir))
}
#[tauri::command]
pub fn open_core_dir() -> CmdResult<()> {
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
wrap_err!(open::that(core_dir))
}
#[tauri::command]
pub fn open_logs_dir() -> CmdResult<()> {
let log_dir = wrap_err!(dirs::app_logs_dir())?;
@@ -214,40 +236,136 @@ pub fn open_web_url(url: String) -> CmdResult<()> {
}
#[cfg(windows)]
pub mod service {
pub mod uwp {
use super::*;
use crate::core::win_service;
use crate::core::win_uwp;
#[tauri::command]
pub async fn check_service() -> CmdResult<win_service::JsonResponse> {
wrap_err!(win_service::check_service().await)
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)
}
}
#[tauri::command]
pub async fn clash_api_get_proxy_delay(
name: String,
url: Option<String>,
timeout: i32,
) -> CmdResult<clash_api::DelayRes> {
match clash_api::get_proxy_delay(name, url, timeout).await {
Ok(res) => Ok(res),
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
}
#[tauri::command]
pub async fn test_delay(url: String) -> CmdResult<u32> {
Ok(feat::test_delay(url).await.unwrap_or(10000u32))
}
#[tauri::command]
pub fn get_app_dir() -> CmdResult<String> {
let app_home_dir = wrap_err!(dirs::app_home_dir())?
.to_string_lossy()
.to_string();
Ok(app_home_dir)
}
#[tauri::command]
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(name);
if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir);
}
if !icon_path.exists() {
let response = wrap_err!(reqwest::get(url).await)?;
let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
let content = wrap_err!(response.bytes().await)?;
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
Ok(icon_path.to_string_lossy().to_string())
}
#[tauri::command]
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
let file_path = std::path::Path::new(&path);
let icon_dir = wrap_err!(dirs::app_home_dir())?.join("icons");
if !icon_dir.exists() {
let _ = std::fs::create_dir_all(&icon_dir);
}
let ext = match file_path.extension() {
Some(e) => e.to_string_lossy().to_string(),
None => "ico".to_string(),
};
let png_dest_path = icon_dir.join(format!("{name}.png"));
let ico_dest_path = icon_dir.join(format!("{name}.ico"));
let dest_path = icon_dir.join(format!("{name}.{ext}"));
if file_path.exists() {
std::fs::remove_file(png_dest_path).unwrap_or_default();
std::fs::remove_file(ico_dest_path).unwrap_or_default();
match std::fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),
}
} else {
return Err("file not found".to_string());
}
}
#[tauri::command]
pub fn open_devtools(app_handle: tauri::AppHandle) {
if let Some(window) = app_handle.get_window("main") {
if !window.is_devtools_open() {
window.open_devtools();
} else {
window.close_devtools();
}
}
}
#[tauri::command]
pub fn exit_app(app_handle: tauri::AppHandle) {
let _ = resolve::save_window_size_position(&app_handle, true);
resolve::resolve_reset();
api::process::kill_children();
app_handle.exit(0);
std::process::exit(0);
}
pub mod service {
use super::*;
use crate::core::service;
#[tauri::command]
pub async fn check_service() -> CmdResult<service::JsonResponse> {
wrap_err!(service::check_service().await)
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
wrap_err!(win_service::install_service().await)
wrap_err!(service::install_service().await)
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult {
wrap_err!(win_service::uninstall_service().await)
wrap_err!(service::uninstall_service().await)
}
}
#[cfg(not(windows))]
pub mod service {
pub mod uwp {
use super::*;
#[tauri::command]
pub async fn check_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
Ok(())
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult {
pub async fn invoke_uwp_tool() -> CmdResult {
Ok(())
}
}
+131 -21
View File
@@ -12,33 +12,66 @@ pub struct IClashTemp(pub Mapping);
impl IClashTemp {
pub fn new() -> Self {
let template = Self::template();
match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
Ok(map) => Self(Self::guard(map)),
Ok(mut map) => {
template.0.keys().for_each(|key| {
if !map.contains_key(key) {
map.insert(key.clone(), template.0.get(key).unwrap().clone());
}
});
Self(Self::guard(map))
}
Err(err) => {
log::error!(target: "app", "{err}");
Self::template()
template
}
}
}
pub fn template() -> Self {
let mut map = Mapping::new();
map.insert("mixed-port".into(), 7890.into());
let mut tun = Mapping::new();
tun.insert("stack".into(), "gvisor".into());
tun.insert("device".into(), "Meta".into());
tun.insert("auto-route".into(), true.into());
tun.insert("strict-route".into(), false.into());
tun.insert("auto-detect-interface".into(), true.into());
tun.insert("dns-hijack".into(), vec!["any:53"].into());
tun.insert("mtu".into(), 1500.into());
#[cfg(not(target_os = "windows"))]
map.insert("redir-port".into(), 7895.into());
#[cfg(target_os = "linux")]
map.insert("tproxy-port".into(), 7896.into());
map.insert("mixed-port".into(), 7897.into());
map.insert("socks-port".into(), 7898.into());
map.insert("port".into(), 7899.into());
map.insert("log-level".into(), "info".into());
map.insert("allow-lan".into(), false.into());
map.insert("mode".into(), "rule".into());
map.insert("external-controller".into(), "127.0.0.1:9090".into());
map.insert("external-controller".into(), "127.0.0.1:9097".into());
map.insert("secret".into(), "".into());
map.insert("tun".into(), tun.into());
Self(map)
}
fn guard(mut config: Mapping) -> Mapping {
let port = Self::guard_mixed_port(&config);
#[cfg(not(target_os = "windows"))]
let redir_port = Self::guard_redir_port(&config);
#[cfg(target_os = "linux")]
let tproxy_port = Self::guard_tproxy_port(&config);
let mixed_port = Self::guard_mixed_port(&config);
let socks_port = Self::guard_socks_port(&config);
let port = Self::guard_port(&config);
let ctrl = Self::guard_server_ctrl(&config);
config.insert("mixed-port".into(), port.into());
#[cfg(not(target_os = "windows"))]
config.insert("redir-port".into(), redir_port.into());
#[cfg(target_os = "linux")]
config.insert("tproxy-port".into(), tproxy_port.into());
config.insert("mixed-port".into(), mixed_port.into());
config.insert("socks-port".into(), socks_port.into());
config.insert("port".into(), port.into());
config.insert("external-controller".into(), ctrl.into());
config
}
@@ -61,12 +94,24 @@ impl IClashTemp {
Self::guard_mixed_port(&self.0)
}
#[allow(unused)]
pub fn get_socks_port(&self) -> u16 {
Self::guard_socks_port(&self.0)
}
#[allow(unused)]
pub fn get_port(&self) -> u16 {
Self::guard_port(&self.0)
}
pub fn get_client_info(&self) -> ClashInfo {
let config = &self.0;
ClashInfo {
port: Self::guard_mixed_port(&config),
server: Self::guard_client_ctrl(&config),
mixed_port: Self::guard_mixed_port(config),
socks_port: Self::guard_socks_port(config),
port: Self::guard_port(config),
server: Self::guard_client_ctrl(config),
secret: config.get("secret").and_then(|value| match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Bool(val_bool) => Some(val_bool.to_string()),
@@ -75,6 +120,37 @@ impl IClashTemp {
}),
}
}
#[cfg(not(target_os = "windows"))]
pub fn guard_redir_port(config: &Mapping) -> u16 {
let mut port = config
.get("redir-port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7895);
if port == 0 {
port = 7895;
}
port
}
#[cfg(target_os = "linux")]
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
let mut port = config
.get("tproxy-port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7896);
if port == 0 {
port = 7896;
}
port
}
pub fn guard_mixed_port(config: &Mapping) -> u16 {
let mut port = config
@@ -84,9 +160,39 @@ impl IClashTemp {
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7890);
.unwrap_or(7897);
if port == 0 {
port = 7890;
port = 7897;
}
port
}
pub fn guard_socks_port(config: &Mapping) -> u16 {
let mut port = config
.get("socks-port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7898);
if port == 0 {
port = 7898;
}
port
}
pub fn guard_port(config: &Mapping) -> u16 {
let mut port = config
.get("port")
.and_then(|value| match value {
Value::String(val_str) => val_str.parse().ok(),
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
_ => None,
})
.unwrap_or(7899);
if port == 0 {
port = 7899;
}
port
}
@@ -98,7 +204,7 @@ impl IClashTemp {
Some(val_str) => {
let val_str = val_str.trim();
let val = match val_str.starts_with(":") {
let val = match val_str.starts_with(':') {
true => format!("127.0.0.1{val_str}"),
false => val_str.to_owned(),
};
@@ -109,7 +215,7 @@ impl IClashTemp {
}
None => None,
})
.unwrap_or("127.0.0.1:9090".into())
.unwrap_or("127.0.0.1:9097".into())
}
pub fn guard_client_ctrl(config: &Mapping) -> String {
@@ -121,7 +227,7 @@ impl IClashTemp {
}
socket.to_string()
}
Err(_) => "127.0.0.1:9090".into(),
Err(_) => "127.0.0.1:9097".into(),
}
}
}
@@ -129,6 +235,8 @@ impl IClashTemp {
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct ClashInfo {
/// clash core port
pub mixed_port: u16,
pub socks_port: u16,
pub port: u16,
/// same as `external-controller`
pub server: String,
@@ -148,7 +256,9 @@ fn test_clash_info() {
fn get_result<S: Into<String>>(port: u16, server: S) -> ClashInfo {
ClashInfo {
port,
mixed_port: port,
socks_port: 7898,
port: 7899,
server: server.into(),
secret: None,
}
@@ -156,12 +266,12 @@ fn test_clash_info() {
assert_eq!(
IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(),
get_result(7890, "127.0.0.1:9090")
get_result(7897, "127.0.0.1:9097")
);
assert_eq!(get_case("", ""), get_result(7890, "127.0.0.1:9090"));
assert_eq!(get_case("", ""), get_result(7897, "127.0.0.1:9097"));
assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9090"));
assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9097"));
assert_eq!(
get_case(8888, "127.0.0.1:8888"),
@@ -170,7 +280,7 @@ fn test_clash_info() {
assert_eq!(
get_case(8888, " :98888 "),
get_result(8888, "127.0.0.1:9090")
get_result(8888, "127.0.0.1:9097")
);
assert_eq!(
@@ -195,7 +305,7 @@ fn test_clash_info() {
assert_eq!(
get_case(8888, "192.168.1.1:80800"),
get_result(8888, "127.0.0.1:9090")
get_result(8888, "127.0.0.1:9097")
);
}
+3 -3
View File
@@ -45,7 +45,7 @@ impl Config {
Self::global().runtime_config.clone()
}
/// 初始化配置
/// 初始化订阅
pub fn init_config() -> Result<()> {
crate::log_err!(Self::generate());
if let Err(err) = Self::generate_file(ConfigType::Run) {
@@ -64,7 +64,7 @@ impl Config {
Ok(())
}
/// 将配置丢到对应的文件中
/// 将订阅丢到对应的文件中
pub fn generate_file(typ: ConfigType) -> Result<PathBuf> {
let path = match typ {
ConfigType::Run => dirs::app_home_dir()?.join(RUNTIME_CONFIG),
@@ -82,7 +82,7 @@ impl Config {
Ok(path)
}
/// 生成配置存好
/// 生成订阅存好
pub fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance();
+1 -1
View File
@@ -26,7 +26,7 @@ macro_rules! draft_define {
}
pub fn draft(&self) -> MappedMutexGuard<$id> {
MutexGuard::map(self.inner.lock(), |mut inner| {
MutexGuard::map(self.inner.lock(), |inner| {
if inner.1.is_none() {
inner.1 = Some(inner.0.clone());
}
+96 -63
View File
@@ -1,4 +1,4 @@
use crate::utils::{dirs, help, tmpl};
use crate::utils::{dirs, help, resolve::VERSION, tmpl};
use anyhow::{bail, Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
@@ -8,7 +8,7 @@ use sysproxy::Sysproxy;
use super::Config;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PrfItem {
pub uid: Option<String>,
@@ -46,6 +46,10 @@ pub struct PrfItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub option: Option<PrfOption>,
/// profile web page url
#[serde(skip_serializing_if = "Option::is_none")]
pub home: Option<String>,
/// the file data
#[serde(skip)]
pub file_data: Option<String>,
@@ -59,10 +63,10 @@ pub struct PrfSelected {
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
pub struct PrfExtra {
pub upload: usize,
pub download: usize,
pub total: usize,
pub expire: usize,
pub upload: u64,
pub download: u64,
pub total: u64,
pub expire: u64,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
@@ -84,6 +88,12 @@ pub struct PrfOption {
#[serde(skip_serializing_if = "Option::is_none")]
pub update_interval: Option<u64>,
/// for `remote` profile
/// disable certificate validation
/// default is `false`
#[serde(skip_serializing_if = "Option::is_none")]
pub danger_accept_invalid_certs: Option<bool>,
}
impl PrfOption {
@@ -93,28 +103,11 @@ impl PrfOption {
a.user_agent = b.user_agent.or(a.user_agent);
a.with_proxy = b.with_proxy.or(a.with_proxy);
a.self_proxy = b.self_proxy.or(a.self_proxy);
a.danger_accept_invalid_certs = b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs);
a.update_interval = b.update_interval.or(a.update_interval);
Some(a)
}
t @ _ => t.0.or(t.1),
}
}
}
impl Default for PrfItem {
fn default() -> Self {
PrfItem {
uid: None,
itype: None,
name: None,
desc: None,
file: None,
url: None,
selected: None,
extra: None,
updated: None,
option: None,
file_data: None,
t => t.0.or(t.1),
}
}
}
@@ -152,7 +145,7 @@ impl PrfItem {
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}\""),
}
}
@@ -172,6 +165,7 @@ impl PrfItem {
selected: None,
extra: None,
option: None,
home: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
})
@@ -188,13 +182,18 @@ impl PrfItem {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
let accept_invalid_certs = opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut builder = reqwest::ClientBuilder::new().no_proxy();
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
// 使用软件自己的代理
if self_proxy {
let port = Config::clash().data().get_mixed_port();
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let proxy_scheme = format!("http://127.0.0.1:{port}");
@@ -210,26 +209,27 @@ impl PrfItem {
}
// 使用系统代理
else if with_proxy {
match Sysproxy::get_system_proxy() {
Ok(p @ Sysproxy { enable: true, .. }) => {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
_ => {}
};
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
}
let version = unsafe { dirs::APP_VERSION };
let version = format!("clash-verge/{version}");
let version = match VERSION.get() {
Some(v) => format!("clash-verge/v{}", v),
None => "clash-verge/unknown".to_string(),
};
builder = builder.danger_accept_invalid_certs(accept_invalid_certs);
builder = builder.user_agent(user_agent.unwrap_or(version));
let resp = builder.build()?.get(url).send().await?;
@@ -245,12 +245,11 @@ impl PrfItem {
let extra = match header.get("Subscription-Userinfo") {
Some(value) => {
let sub_info = value.to_str().unwrap_or("");
Some(PrfExtra {
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
download: help::parse_str(sub_info, "download=").unwrap_or(0),
total: help::parse_str(sub_info, "total=").unwrap_or(0),
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
upload: help::parse_str(sub_info, "upload").unwrap_or(0),
download: help::parse_str(sub_info, "download").unwrap_or(0),
total: help::parse_str(sub_info, "total").unwrap_or(0),
expire: help::parse_str(sub_info, "expire").unwrap_or(0),
})
}
None => None,
@@ -259,20 +258,48 @@ impl PrfItem {
// parse the Content-Disposition
let filename = match header.get("Content-Disposition") {
Some(value) => {
let filename = value.to_str().unwrap_or("");
help::parse_str::<String>(filename, "filename=")
let filename = format!("{value:?}");
let filename = filename.trim_matches('"');
match help::parse_str::<String>(filename, "filename*") {
Some(filename) => {
let iter = percent_encoding::percent_decode(filename.as_bytes());
let filename = iter.decode_utf8().unwrap_or_default();
filename.split("''").last().map(|s| s.to_string())
}
None => match help::parse_str::<String>(filename, "filename") {
Some(filename) => {
let filename = filename.trim_matches('"');
Some(filename.to_string())
}
None => None,
},
}
}
None => None,
None => Some(
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
),
};
let option = match update_interval {
Some(val) => Some(PrfOption {
update_interval: Some(val),
..PrfOption::default()
}),
None => match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Err(_) => None,
},
None => None,
},
};
// parse the profile-update-interval
let option = match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Err(_) => None,
let home = match header.get("profile-web-page-url") {
Some(value) => {
let str_value = value.to_str().unwrap_or("");
Some(str_value.to_string())
},
None => None,
};
@@ -282,8 +309,11 @@ impl PrfItem {
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
let data = resp.text_with_charset("utf-8").await?;
// process the charset "UTF-8 with BOM"
let data = data.trim_start_matches('\u{feff}');
// check the data whether the valid yaml format
let yaml = serde_yaml::from_str::<Mapping>(&data) //
let yaml = serde_yaml::from_str::<Mapping>(data)
.context("the remote profile data is invalid yaml")?;
if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") {
@@ -300,8 +330,9 @@ impl PrfItem {
selected: None,
extra,
option,
home,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(data),
file_data: Some(data.into()),
})
}
@@ -321,6 +352,7 @@ impl PrfItem {
selected: None,
extra: None,
option: None,
home: None,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(tmpl::ITEM_MERGE.into()),
})
@@ -339,6 +371,7 @@ impl PrfItem {
desc: Some(desc),
file: Some(file),
url: None,
home: None,
selected: None,
extra: None,
option: None,
+45 -27
View File
@@ -14,9 +14,6 @@ pub struct IProfiles {
/// same as PrfConfig.chain
pub chain: Option<Vec<String>>,
/// record valid fields for clash
pub valid: Option<Vec<String>>,
/// profile list
pub items: Option<Vec<PrfItem>>,
}
@@ -37,13 +34,13 @@ impl IProfiles {
profiles.items = Some(vec![]);
}
// compatible with the old old old version
profiles.items.as_mut().map(|items| {
for mut item in items.iter_mut() {
if let Some(items) = profiles.items.as_mut() {
for item in items.iter_mut() {
if item.uid.is_none() {
item.uid = Some(help::get_uid("d"));
}
}
});
}
profiles
}
Err(err) => {
@@ -55,7 +52,6 @@ impl IProfiles {
pub fn template() -> Self {
Self {
valid: Some(vec!["dns".into()]),
items: Some(vec![]),
..Self::default()
}
@@ -88,10 +84,6 @@ impl IProfiles {
self.chain = Some(chain);
}
if let Some(valid) = patch.valid {
self.valid = Some(valid);
}
Ok(())
}
@@ -138,24 +130,50 @@ impl IProfiles {
let path = dirs::app_profiles_dir()?.join(&file);
fs::File::create(path)
.context(format!("failed to create file \"{}\"", file))?
.with_context(|| format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.context(format!("failed to write to file \"{}\"", file))?;
.with_context(|| format!("failed to write to file \"{}\"", file))?;
}
if self.items.is_none() {
self.items = Some(vec![]);
}
self.items.as_mut().map(|items| items.push(item));
if let Some(items) = self.items.as_mut() {
items.push(item)
}
self.save_file()
}
/// reorder items
pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
let mut items = self.items.take().unwrap_or_default();
let mut old_index = None;
let mut new_index = None;
for (i, _) in items.iter().enumerate() {
if items[i].uid == Some(active_id.clone()) {
old_index = Some(i);
}
if items[i].uid == Some(over_id.clone()) {
new_index = Some(i);
}
}
if old_index.is_none() || new_index.is_none() {
return Ok(());
}
let item = items.remove(old_index.unwrap());
items.insert(new_index.unwrap(), item);
self.items = Some(items);
self.save_file()
}
/// update the item value
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]);
let mut items = self.items.take().unwrap_or_default();
for mut each in items.iter_mut() {
for each in items.iter_mut() {
if each.uid == Some(uid.clone()) {
patch!(each, item, itype);
patch!(each, item, name);
@@ -189,11 +207,11 @@ impl IProfiles {
if let Some(items) = self.items.as_mut() {
let some_uid = Some(uid.clone());
for mut each in items.iter_mut() {
for each in items.iter_mut() {
if each.uid == some_uid {
each.extra = item.extra;
each.updated = item.updated;
each.home = item.home;
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {
@@ -207,9 +225,9 @@ impl IProfiles {
let path = dirs::app_profiles_dir()?.join(&file);
fs::File::create(path)
.context(format!("failed to create file \"{}\"", file))?
.with_context(|| format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.context(format!("failed to write to file \"{}\"", file))?;
.with_context(|| format!("failed to write to file \"{}\"", file))?;
}
break;
@@ -226,11 +244,11 @@ impl IProfiles {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let mut items = self.items.take().unwrap_or(vec![]);
let mut items = self.items.take().unwrap_or_default();
let mut index = None;
// get the index
for i in 0..items.len() {
for (i, _) in items.iter().enumerate() {
if items[i].uid == Some(uid.clone()) {
index = Some(i);
break;
@@ -238,19 +256,19 @@ impl IProfiles {
}
if let Some(index) = index {
items.remove(index).file.map(|file| {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
});
}
}
// delete the original uid
if current == uid {
self.current = match items.len() > 0 {
self.current = match !items.is_empty() {
true => items[0].uid.clone(),
false => None,
};
@@ -261,7 +279,7 @@ impl IProfiles {
Ok(current == uid)
}
/// 获取current指向的配置内容
/// 获取current指向的订阅内容
pub fn current_mapping(&self) -> Result<Mapping> {
match (self.current.as_ref(), self.items.as_ref()) {
(Some(current), Some(items)) => {
@@ -270,7 +288,7 @@ impl IProfiles {
Some(file) => dirs::app_profiles_dir()?.join(file),
None => bail!("failed to get the file field"),
};
return Ok(help::read_merge_mapping(&file_path)?);
return help::read_merge_mapping(&file_path);
}
bail!("failed to find the current profile \"uid:{current}\"");
}
+30 -3
View File
@@ -1,11 +1,11 @@
use crate::enhance::field::use_keys;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use serde_yaml::{Mapping, Value};
use std::collections::HashMap;
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IRuntime {
pub config: Option<Mapping>,
// 记录在配置中(包括merge和script生成的)出现过的keys
// 记录在订阅中(包括merge和script生成的)出现过的keys
// 这些keys不一定都生效
pub exists_keys: Vec<String>,
pub chain_logs: HashMap<String, Vec<(String, String)>>,
@@ -15,4 +15,31 @@ impl IRuntime {
pub fn new() -> Self {
Self::default()
}
// 这里只更改 allow-lan | ipv6 | log-level | tun
pub fn patch_config(&mut self, patch: Mapping) {
if let Some(config) = self.config.as_mut() {
["allow-lan", "ipv6", "log-level"]
.into_iter()
.for_each(|key| {
if let Some(value) = patch.get(key).to_owned() {
config.insert(key.into(), value.clone());
}
});
let tun = config.get("tun");
let mut tun = tun.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
let patch_tun = patch.get("tun");
let patch_tun = patch_tun.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
use_keys(&patch_tun).into_iter().for_each(|key| {
if let Some(value) = patch_tun.get(&key).to_owned() {
tun.insert(key.into(), value.clone());
}
});
config.insert("tun".into(), Value::from(tun));
}
}
}
+147 -7
View File
@@ -1,5 +1,6 @@
use crate::utils::{dirs, help};
use anyhow::Result;
use log::LevelFilter;
use serde::{Deserialize, Serialize};
/// ### `verge.yaml` schema
@@ -8,19 +9,48 @@ pub struct IVerge {
/// app listening port for app singleton
pub app_singleton_port: Option<u16>,
/// app log level
/// silent | error | warn | info | debug | trace
pub app_log_level: Option<String>,
// i18n
pub language: Option<String>,
/// `light` or `dark` or `system`
pub theme_mode: Option<String>,
/// enable blur mode
/// maybe be able to set the alpha
pub theme_blur: Option<bool>,
/// tray click event
pub tray_event: Option<String>,
/// copy env type
pub env_type: Option<String>,
/// start page
pub start_page: Option<String>,
/// startup script path
pub startup_script: Option<String>,
/// enable traffic graph default is true
pub traffic_graph: Option<bool>,
/// show memory info (only for Clash Meta)
pub enable_memory_usage: Option<bool>,
/// enable group icon
pub enable_group_icon: Option<bool>,
/// common tray icon
pub common_tray_icon: Option<bool>,
/// menu icon
pub menu_icon: Option<String>,
/// sysproxy tray icon
pub sysproxy_tray_icon: Option<bool>,
/// tun tray icon
pub tun_tray_icon: Option<bool>,
/// clash tun mode
pub enable_tun_mode: Option<bool>,
@@ -63,11 +93,59 @@ pub struct IVerge {
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
/// 是否自动检查更新
pub auto_check_update: Option<bool>,
/// 默认的延迟测试连接
pub default_latency_test: Option<String>,
/// 默认的延迟测试超时时间
pub default_latency_timeout: Option<i32>,
/// 是否使用内部的脚本支持,默认为真
pub enable_builtin_enhanced: Option<bool>,
/// proxy 页面布局 列数
pub proxy_layout_column: Option<i32>,
/// 测试网站列表
pub test_list: Option<Vec<IVergeTestItem>>,
/// 日志清理
/// 0: 不清理; 1: 7天; 2: 30天; 3: 90天
pub auto_log_clean: Option<i32>,
/// window size and position
#[serde(skip_serializing_if = "Option::is_none")]
pub window_size_position: Option<Vec<f64>>,
/// window size and position
#[serde(skip_serializing_if = "Option::is_none")]
pub window_is_maximized: Option<bool>,
/// 是否启用随机端口
pub enable_random_port: Option<bool>,
/// verge 的各种 port 用于覆盖 clash 的各种 port
#[cfg(not(target_os = "windows"))]
pub verge_redir_port: Option<u16>,
#[cfg(target_os = "linux")]
pub verge_tproxy_port: Option<u16>,
pub verge_mixed_port: Option<u16>,
pub verge_socks_port: Option<u16>,
pub verge_port: Option<u16>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IVergeTestItem {
pub uid: Option<String>,
pub name: Option<String>,
pub icon: Option<String>,
pub url: Option<String>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -99,18 +177,38 @@ impl IVerge {
pub fn template() -> Self {
Self {
clash_core: Some("clash".into()),
language: Some("en".into()),
clash_core: Some("clash-meta".into()),
language: Some("zh".into()),
theme_mode: Some("system".into()),
theme_blur: Some(false),
#[cfg(not(target_os = "windows"))]
env_type: Some("bash".into()),
#[cfg(target_os = "windows")]
env_type: Some("powershell".into()),
start_page: Some("/".into()),
traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_group_icon: Some(true),
menu_icon: Some("monochrome".into()),
common_tray_icon: Some(false),
sysproxy_tray_icon: Some(false),
tun_tray_icon: Some(false),
enable_auto_launch: Some(false),
enable_silent_start: Some(false),
enable_system_proxy: Some(false),
enable_random_port: Some(false),
#[cfg(not(target_os = "windows"))]
verge_redir_port: Some(7895),
#[cfg(target_os = "linux")]
verge_tproxy_port: Some(7896),
verge_mixed_port: Some(7897),
verge_socks_port: Some(7898),
verge_port: Some(7899),
enable_proxy_guard: Some(false),
proxy_guard_duration: Some(30),
auto_close_connection: Some(true),
auto_check_update: Some(true),
enable_builtin_enhanced: Some(true),
auto_log_clean: Some(3),
..Self::default()
}
}
@@ -131,15 +229,33 @@ impl IVerge {
};
}
patch!(app_log_level);
patch!(language);
patch!(theme_mode);
patch!(theme_blur);
patch!(tray_event);
patch!(env_type);
patch!(start_page);
patch!(startup_script);
patch!(traffic_graph);
patch!(enable_memory_usage);
patch!(enable_group_icon);
patch!(menu_icon);
patch!(common_tray_icon);
patch!(sysproxy_tray_icon);
patch!(tun_tray_icon);
patch!(enable_tun_mode);
patch!(enable_service_mode);
patch!(enable_auto_launch);
patch!(enable_silent_start);
patch!(enable_random_port);
#[cfg(not(target_os = "windows"))]
patch!(verge_redir_port);
#[cfg(target_os = "linux")]
patch!(verge_tproxy_port);
patch!(verge_mixed_port);
patch!(verge_socks_port);
patch!(verge_port);
patch!(enable_system_proxy);
patch!(enable_proxy_guard);
patch!(system_proxy_bypass);
@@ -151,8 +267,15 @@ impl IVerge {
patch!(hotkeys);
patch!(auto_close_connection);
patch!(auto_check_update);
patch!(default_latency_test);
patch!(default_latency_timeout);
patch!(enable_builtin_enhanced);
patch!(proxy_layout_column);
patch!(test_list);
patch!(auto_log_clean);
patch!(window_size_position);
patch!(window_is_maximized);
}
/// 在初始化前尝试拿到单例端口的值
@@ -167,4 +290,21 @@ impl IVerge {
Err(_) => SERVER_PORT, // 这里就不log错误了
}
}
/// 获取日志等级
pub fn get_log_level(&self) -> LevelFilter {
if let Some(level) = self.app_log_level.as_ref() {
match level.to_lowercase().as_str() {
"silent" => LevelFilter::Off,
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => LevelFilter::Info,
}
} else {
LevelFilter::Info
}
}
}
+40 -6
View File
@@ -1,6 +1,7 @@
use crate::config::Config;
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::collections::HashMap;
@@ -19,7 +20,7 @@ pub async fn put_configs(path: &str) -> Result<()> {
match response.status().as_u16() {
204 => Ok(()),
status @ _ => {
status => {
bail!("failed to put configs with status \"{status}\"")
}
}
@@ -36,6 +37,36 @@ pub async fn patch_configs(config: &Mapping) -> Result<()> {
Ok(())
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct DelayRes {
delay: u64,
}
/// GET /proxies/{name}/delay
/// 获取代理延迟
pub async fn get_proxy_delay(
name: String,
test_url: Option<String>,
timeout: i32,
) -> Result<DelayRes> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/proxies/{name}/delay");
let default_url = "http://1.1.1.1";
let test_url = test_url
.map(|s| if s.is_empty() { default_url.into() } else { s })
.unwrap_or(default_url.into());
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
let builder = client
.get(&url)
.headers(headers)
.query(&[("timeout", &format!("{timeout}")), ("url", &test_url)]);
let response = builder.send().await?;
Ok(response.json::<DelayRes>().await?)
}
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
let client = { Config::clash().data().get_client_info() };
@@ -55,10 +86,13 @@ fn clash_client_info() -> Result<(String, HeaderMap)> {
/// 缩短clash的日志
pub fn parse_log(log: String) -> String {
if log.starts_with("time=") {
return (&log[33..]).to_owned();
if log.starts_with("time=") && log.len() > 33 {
return (log[33..]).to_owned();
}
(&log[9..]).to_owned()
if log.len() > 9 {
return (log[9..]).to_owned();
}
log
}
/// 缩短clash -t的错误输出
@@ -75,7 +109,7 @@ pub fn parse_check_output(log: String) -> String {
};
if mr > m {
return (&log[e..mr]).to_owned();
return (log[e..mr]).to_owned();
}
}
@@ -83,7 +117,7 @@ pub fn parse_check_output(log: String) -> String {
let r = log.find("path=").or(Some(log.len()));
if let (Some(l), Some(r)) = (l, r) {
return (&log[(l + 6)..(r - 1)]).to_owned();
return (log[(l + 6)..(r - 1)]).to_owned();
}
log
+81 -42
View File
@@ -1,3 +1,4 @@
use super::service;
use super::{clash_api, logger::Logger};
use crate::log_err;
use crate::{config::*, utils::dirs};
@@ -5,7 +6,7 @@ use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{fs, io::Write, sync::Arc, time::Duration};
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
use sysinfo::{Pid, System};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep;
@@ -35,12 +36,12 @@ impl CoreManager {
.map(|pid| {
let mut system = System::new();
system.refresh_all();
system.process(Pid::from_u32(pid)).map(|proc| {
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 {
@@ -51,7 +52,7 @@ impl CoreManager {
Ok(())
}
/// 检查配置是否正确
/// 检查订阅是否正确
pub fn check_config(&self) -> Result<()> {
let config_path = Config::generate_file(ConfigType::Check)?;
let config_path = dirs::path_to_str(&config_path)?;
@@ -68,6 +69,10 @@ impl CoreManager {
if !output.status.success() {
let error = clash_api::parse_check_output(output.stdout.clone());
let error = match !error.is_empty() {
true => error,
false => output.stdout.clone(),
};
Logger::global().set_log(output.stdout);
bail!("{error}");
}
@@ -89,10 +94,9 @@ impl CoreManager {
None => false,
};
#[cfg(target_os = "windows")]
if *self.use_service_mode.lock() {
log::debug!(target: "app", "stop the core by service");
log_err!(super::win_service::stop_core_by_service().await);
log_err!(service::stop_core_by_service().await);
should_kill = true;
}
@@ -101,32 +105,27 @@ impl CoreManager {
sleep(Duration::from_millis(500)).await;
}
#[cfg(target_os = "windows")]
{
use super::win_service;
// 服务模式
let enable = { Config::verge().latest().enable_service_mode };
let enable = enable.unwrap_or(false);
// 服务模式
let enable = { Config::verge().latest().enable_service_mode.clone() };
let enable = enable.unwrap_or(false);
*self.use_service_mode.lock() = enable;
*self.use_service_mode.lock() = enable;
if enable {
// 服务模式启动失败就直接运行sidecar
log::debug!(target: "app", "try to run core in service mode");
if enable {
// 服务模式启动失败就直接运行sidecar
log::debug!(target: "app", "try to run core in service mode");
match (|| async {
win_service::check_service().await?;
win_service::run_core_by_service(&config_path).await
})()
.await
{
Ok(_) => return Ok(()),
Err(err) => {
// 修改这个值,免得stop出错
*self.use_service_mode.lock() = false;
log::error!(target: "app", "{err}");
}
match (|| async {
service::check_service().await?;
service::run_core_by_service(&config_path).await
})()
.await
{
Ok(_) => return Ok(()),
Err(err) => {
// 修改这个值,免得stop出错
*self.use_service_mode.lock() = false;
log::error!(target: "app", "{err}");
}
}
}
@@ -136,12 +135,13 @@ impl CoreManager {
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into());
let is_clash = clash_core == "clash";
let config_path = dirs::path_to_str(&config_path)?;
// fix #212
let args = match clash_core.as_str() {
"clash-meta" => vec!["-m", "-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],
};
@@ -161,18 +161,23 @@ impl CoreManager {
let mut sidecar = self.sidecar.lock();
*sidecar = Some(cmd_child);
drop(sidecar);
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => {
let stdout = clash_api::parse_log(line.clone());
log::info!(target: "app", "[clash]: {stdout}");
if is_clash {
let stdout = clash_api::parse_log(line.clone());
log::info!(target: "app", "[clash]: {stdout}");
} else {
log::info!(target: "app", "[clash]: {line}");
};
Logger::global().set_log(line);
}
CommandEvent::Stderr(err) => {
let stdout = clash_api::parse_log(err.clone());
log::error!(target: "app", "[clash]: {stdout}");
// let stdout = clash_api::parse_log(err.clone());
log::error!(target: "app", "[clash]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Error(err) => {
@@ -181,6 +186,7 @@ impl CoreManager {
}
CommandEvent::Terminated(_) => {
log::info!(target: "app", "clash core terminated");
let _ = CoreManager::global().recover_core();
break;
}
_ => {}
@@ -191,13 +197,45 @@ impl CoreManager {
Ok(())
}
/// 重启内核
pub fn recover_core(&'static self) -> Result<()> {
// 服务模式不管
if *self.use_service_mode.lock() {
return Ok(());
}
// 清空原来的sidecar值
if let Some(sidecar) = self.sidecar.lock().take() {
let _ = sidecar.kill();
}
tauri::async_runtime::spawn(async move {
// 6秒之后再查看服务是否正常 (时间随便搞的)
// terminated 可能是切换内核 (切换内核已经有500ms的延迟)
sleep(Duration::from_millis(6666)).await;
if self.sidecar.lock().is_none() {
log::info!(target: "app", "recover clash core");
// 重新启动app
if let Err(err) = self.run_core().await {
log::error!(target: "app", "failed to recover clash core");
log::error!(target: "app", "{err}");
let _ = self.recover_core();
}
}
});
Ok(())
}
/// 停止核心运行
pub fn stop_core(&self) -> Result<()> {
#[cfg(target_os = "windows")]
if *self.use_service_mode.lock() {
log::debug!(target: "app", "stop the core by service");
tauri::async_runtime::block_on(async move {
log_err!(super::win_service::stop_core_by_service().await);
log_err!(service::stop_core_by_service().await);
});
return Ok(());
}
@@ -213,8 +251,9 @@ impl CoreManager {
/// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
const CLASH_CORES: [&str; 2] = ["clash-meta", "clash-meta-alpha"];
if &clash_core != "clash" && &clash_core != "clash-meta" {
if !CLASH_CORES.contains(&clash_core.as_str()) {
bail!("invalid clash core name \"{clash_core}\"");
}
@@ -222,7 +261,7 @@ impl CoreManager {
Config::verge().draft().clash_core = Some(clash_core);
// 更新配置
// 更新订阅
Config::generate()?;
self.check_config()?;
@@ -250,13 +289,13 @@ impl CoreManager {
pub async fn update_config(&self) -> Result<()> {
log::debug!(target: "app", "try to update clash config");
// 更新配置
// 更新订阅
Config::generate()?;
// 检查配置是否正常
// 检查订阅是否正常
self.check_config()?;
// 更新运行时配置
// 更新运行时订阅
let path = Config::generate_file(ConfigType::Run)?;
let path = dirs::path_to_str(&path)?;
+1 -1
View File
@@ -28,7 +28,7 @@ impl Handle {
self.app_handle
.lock()
.as_ref()
.map_or(None, |a| a.get_window("main"))
.and_then(|a| a.get_window("main"))
}
pub fn refresh_clash() {
+6 -26
View File
@@ -4,7 +4,6 @@ use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use tauri::{AppHandle, GlobalShortcutManager};
use tauri_runtime_wry::wry::application::accelerator::Accelerator;
pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
@@ -35,7 +34,7 @@ impl Hotkey {
match (key, func) {
(Some(key), Some(func)) => {
log_err!(Self::check_key(key).and_then(|_| self.register(key, func)));
log_err!(self.register(key, func));
}
_ => {
let key = key.unwrap_or("None");
@@ -50,16 +49,6 @@ impl Hotkey {
Ok(())
}
/// 检查一个键是否合法
fn check_key(hotkey: &str) -> Result<()> {
// fix #287
// tauri的这几个方法全部有Result expect,会panic,先检测一遍避免挂了
if hotkey.parse::<Accelerator>().is_err() {
bail!("invalid hotkey `{hotkey}`");
}
Ok(())
}
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
let app_handle = self.app_handle.lock();
if app_handle.is_none() {
@@ -76,16 +65,12 @@ impl Hotkey {
}
let f = match func.trim() {
"open_or_close_dashboard" => feat::open_or_close_dashboard,
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"clash_mode_script" => || feat::change_clash_mode("script".into()),
"toggle_system_proxy" => || feat::toggle_system_proxy(),
"enable_system_proxy" => || feat::enable_system_proxy(),
"disable_system_proxy" => || feat::disable_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(),
"enable_tun_mode" => || feat::enable_tun_mode(),
"disable_tun_mode" => || feat::disable_tun_mode(),
"toggle_system_proxy" => feat::toggle_system_proxy,
"toggle_tun_mode" => feat::toggle_tun_mode,
_ => bail!("invalid function \"{func}\""),
};
@@ -96,7 +81,7 @@ impl Hotkey {
}
fn unregister(&self, hotkey: &str) -> Result<()> {
self.get_manager()?.unregister(&hotkey)?;
self.get_manager()?.unregister(hotkey)?;
log::info!(target: "app", "unregister hotkey {hotkey}");
Ok(())
}
@@ -108,11 +93,6 @@ impl Hotkey {
let (del, add) = Self::get_diff(old_map, new_map);
// 先检查一遍所有新的热键是不是可以用的
for (hotkey, _) in add.iter() {
Self::check_key(hotkey)?;
}
del.iter().for_each(|key| {
let _ = self.unregister(key);
});
@@ -125,7 +105,7 @@ impl Hotkey {
Ok(())
}
fn get_map_from_vec<'a>(hotkeys: &'a Vec<String>) -> HashMap<&'a str, &'a str> {
fn get_map_from_vec(hotkeys: &Vec<String>) -> HashMap<&str, &str> {
let mut map = HashMap::new();
hotkeys.iter().for_each(|hotkey| {
+47
View File
@@ -0,0 +1,47 @@
/// 给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}");
}
}
+3 -1
View File
@@ -3,9 +3,11 @@ mod core;
pub mod handle;
pub mod hotkey;
pub mod logger;
pub mod manager;
pub mod sysopt;
pub mod timer;
pub mod tray;
pub mod win_service;
pub mod service;
pub mod win_uwp;
pub use self::core::*;
@@ -1,18 +1,15 @@
#![cfg(target_os = "windows")]
use crate::config::Config;
use crate::utils::dirs;
use anyhow::{bail, Context, Result};
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::os::windows::process::CommandExt;
use std::path::PathBuf;
use std::time::Duration;
use std::{env::current_exe, process::Command as StdCommand};
use tokio::time::sleep;
// Windows only
const SERVICE_URL: &str = "http://127.0.0.1:33211";
#[derive(Debug, Deserialize, Serialize, Clone)]
@@ -32,7 +29,13 @@ pub struct JsonResponse {
/// Install the Clash Verge Service
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
///
#[cfg(target_os = "windows")]
pub async fn install_service() -> Result<()> {
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::os::windows::process::CommandExt;
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service.exe");
@@ -60,9 +63,69 @@ pub async fn install_service() -> Result<()> {
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn install_service() -> Result<()> {
use users::get_effective_uid;
let binary_path = dirs::service_path()?;
let installer_path = binary_path.with_file_name("install-service");
if !installer_path.exists() {
bail!("installer not found");
}
let elevator = crate::utils::unix_helper::linux_elevator();
let status = match get_effective_uid() {
0 => StdCommand::new(installer_path).status()?,
_ => StdCommand::new(elevator)
.arg("sh")
.arg("-c")
.arg(installer_path)
.status()?,
};
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "macos")]
pub async fn install_service() -> Result<()> {
let binary_path = dirs::service_path()?;
let installer_path = binary_path.with_file_name("install-service");
if !installer_path.exists() {
bail!("installer not found");
}
let shell = installer_path.to_string_lossy().replace(" ", "\\\\ ");
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
/// Uninstall the Clash Verge Service
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
#[cfg(target_os = "windows")]
pub async fn uninstall_service() -> Result<()> {
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::os::windows::process::CommandExt;
let binary_path = dirs::service_path()?;
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
@@ -90,6 +153,63 @@ pub async fn uninstall_service() -> Result<()> {
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn uninstall_service() -> Result<()> {
use users::get_effective_uid;
let binary_path = dirs::service_path()?;
let uninstaller_path = binary_path.with_file_name("uninstall-service");
if !uninstaller_path.exists() {
bail!("uninstaller not found");
}
let elevator = crate::utils::unix_helper::linux_elevator();
let status = match get_effective_uid() {
0 => StdCommand::new(uninstaller_path).status()?,
_ => StdCommand::new(elevator)
.arg("sh")
.arg("-c")
.arg(uninstaller_path)
.status()?,
};
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "macos")]
pub async fn uninstall_service() -> Result<()> {
let binary_path = dirs::service_path()?;
let uninstaller_path = binary_path.with_file_name("uninstall-service");
if !uninstaller_path.exists() {
bail!("uninstaller not found");
}
let shell = uninstaller_path.to_string_lossy().replace(" ", "\\\\ ");
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
/// check the windows service status
pub async fn check_service() -> Result<JsonResponse> {
let url = format!("{SERVICE_URL}/get_clash");
@@ -119,7 +239,8 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("clash".into());
let clash_bin = format!("{clash_core}.exe");
let bin_ext = if cfg!(windows) { ".exe" } else { "" };
let clash_bin = format!("{clash_core}{bin_ext}");
let bin_path = current_exe()?.with_file_name(clash_bin);
let bin_path = dirs::path_to_str(&bin_path)?;
+48 -30
View File
@@ -3,9 +3,10 @@ use anyhow::{anyhow, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::env::current_exe;
use std::sync::Arc;
use sysproxy::Sysproxy;
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
use tauri::async_runtime::Mutex as TokioMutex;
pub struct Sysopt {
/// current system proxy setting
@@ -23,11 +24,12 @@ pub struct Sysopt {
}
#[cfg(target_os = "windows")]
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;<local>";
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;<local>";
#[cfg(target_os = "linux")]
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::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")]
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
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>";
impl Sysopt {
pub fn global() -> &'static Sysopt {
@@ -43,13 +45,16 @@ impl Sysopt {
/// init the sysproxy
pub fn init_sysproxy(&self) -> Result<()> {
let port = { Config::clash().latest().get_mixed_port() };
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let (enable, bypass) = {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.clone().unwrap_or(false),
verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(),
)
};
@@ -62,7 +67,7 @@ impl Sysopt {
};
if enable {
let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
let old = Sysproxy::get_system_proxy().ok();
current.set_system_proxy()?;
*self.old_sysproxy.lock() = old;
@@ -89,7 +94,7 @@ impl Sysopt {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.clone().unwrap_or(false),
verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(),
)
};
@@ -98,6 +103,12 @@ impl Sysopt {
sysproxy.enable = enable;
sysproxy.bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
sysproxy.port = port;
sysproxy.set_system_proxy()?;
*cur_sysproxy = Some(sysproxy);
@@ -138,11 +149,8 @@ impl Sysopt {
/// init the auto launch
pub fn init_launch(&self) -> Result<()> {
let enable = { Config::verge().latest().enable_auto_launch.clone() };
let enable = enable.unwrap_or(false);
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
// let app_exe = dunce::canonicalize(app_exe)?;
let app_name = app_exe
.file_stem()
.and_then(|f| f.to_str())
@@ -171,24 +179,29 @@ impl Sysopt {
})()
.unwrap_or(app_path);
// fix #403
#[cfg(target_os = "linux")]
let app_path = {
use crate::core::handle::Handle;
use tauri::Manager;
let handle = Handle::global();
match handle.app_handle.lock().as_ref() {
Some(app_handle) => {
let appimage = app_handle.env().appimage;
appimage
.and_then(|p| p.to_str().map(|s| s.to_string()))
.unwrap_or(app_path)
}
None => app_path,
}
};
let auto = AutoLaunchBuilder::new()
.set_app_name(app_name)
.set_app_path(&app_path)
.build()?;
// 避免在开发时将自启动关了
#[cfg(feature = "verge-dev")]
if !enable {
return Ok(());
}
// macos每次启动都更新登录项,避免重复设置登录项
#[cfg(target_os = "macos")]
let _ = auto.disable();
if enable {
auto.enable()?;
}
*self.auto_launch.lock() = Some(auto);
Ok(())
@@ -202,7 +215,7 @@ impl Sysopt {
drop(auto_launch);
return self.init_launch();
}
let enable = { Config::verge().latest().enable_auto_launch.clone() };
let enable = { Config::verge().latest().enable_auto_launch };
let enable = enable.unwrap_or(false);
let auto_launch = auto_launch.as_ref().unwrap();
@@ -240,9 +253,9 @@ impl Sysopt {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.clone().unwrap_or(false),
verge.enable_proxy_guard.clone().unwrap_or(false),
verge.proxy_guard_duration.clone().unwrap_or(10),
verge.enable_system_proxy.unwrap_or(false),
verge.enable_proxy_guard.unwrap_or(false),
verge.proxy_guard_duration.unwrap_or(10),
verge.system_proxy_bypass.clone(),
)
};
@@ -257,7 +270,12 @@ impl Sysopt {
log::debug!(target: "app", "try to guard the system proxy");
let port = { Config::clash().latest().get_mixed_port() };
let port = {
Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port())
};
let sysproxy = Sysproxy {
enable: true,
+2 -2
View File
@@ -40,7 +40,7 @@ impl Timer {
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
Config::profiles().latest().get_items().map(|items| {
if let Some(items) = Config::profiles().latest().get_items() {
items
.iter()
.filter_map(|item| {
@@ -61,7 +61,7 @@ impl Timer {
}
}
})
});
}
Ok(())
}
+255 -60
View File
@@ -1,4 +1,9 @@
use crate::{config::Config, feat, utils::resolve};
use crate::{
cmds,
config::Config,
feat,
utils::{dirs, resolve},
};
use anyhow::Result;
use tauri::{
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
@@ -13,53 +18,77 @@ impl Tray {
let version = app_handle.package_info().version.to_string();
if zh {
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("open_window", "打开面板"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("rule_mode", "规则模式"))
.add_item(CustomMenuItem::new("global_mode", "全局模式"))
.add_item(CustomMenuItem::new("direct_mode", "直连模式"))
.add_item(CustomMenuItem::new("script_mode", "脚本模式"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("system_proxy", "系统代理"))
.add_item(CustomMenuItem::new("tun_mode", "TUN 模式"))
.add_submenu(SystemTraySubmenu::new(
"更多",
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("restart_clash", "重启 Clash"))
.add_item(CustomMenuItem::new("restart_app", "重启应用"))
.add_item(
CustomMenuItem::new("app_version", format!("Version {version}"))
.disabled(),
),
))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit", "退出").accelerator("CmdOrControl+Q"))
} else {
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("open_window", "Dashboard"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("rule_mode", "Rule Mode"))
.add_item(CustomMenuItem::new("global_mode", "Global Mode"))
.add_item(CustomMenuItem::new("direct_mode", "Direct Mode"))
.add_item(CustomMenuItem::new("script_mode", "Script Mode"))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("system_proxy", "System Proxy"))
.add_item(CustomMenuItem::new("tun_mode", "Tun Mode"))
.add_submenu(SystemTraySubmenu::new(
"More",
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("restart_clash", "Restart Clash"))
.add_item(CustomMenuItem::new("restart_app", "Restart App"))
.add_item(
CustomMenuItem::new("app_version", format!("Version {version}"))
.disabled(),
),
))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit", "Quit").accelerator("CmdOrControl+Q"))
macro_rules! t {
($en: expr, $zh: expr) => {
if zh {
$zh
} else {
$en
}
};
}
SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"open_window",
t!("Dashboard", "打开面板"),
))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new(
"rule_mode",
t!("Rule Mode", "规则模式"),
))
.add_item(CustomMenuItem::new(
"global_mode",
t!("Global Mode", "全局模式"),
))
.add_item(CustomMenuItem::new(
"direct_mode",
t!("Direct Mode", "直连模式"),
))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new(
"system_proxy",
t!("System Proxy", "系统代理"),
))
.add_item(CustomMenuItem::new("tun_mode", t!("TUN Mode", "Tun 模式")))
.add_item(CustomMenuItem::new(
"copy_env",
t!("Copy Env", "复制环境变量"),
))
.add_submenu(SystemTraySubmenu::new(
t!("Open Dir", "打开目录"),
SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"open_app_dir",
t!("App Dir", "应用目录"),
))
.add_item(CustomMenuItem::new(
"open_core_dir",
t!("Core Dir", "内核目录"),
))
.add_item(CustomMenuItem::new(
"open_logs_dir",
t!("Logs Dir", "日志目录"),
)),
))
.add_submenu(SystemTraySubmenu::new(
t!("More", "更多"),
SystemTrayMenu::new()
.add_item(CustomMenuItem::new(
"restart_clash",
t!("Restart Clash", "重启 Clash"),
))
.add_item(CustomMenuItem::new(
"restart_app",
t!("Restart App", "重启应用"),
))
.add_item(
CustomMenuItem::new("app_version", format!("Version {version}")).disabled(),
),
))
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(CustomMenuItem::new("quit", t!("Quit", "退出")).accelerator("CmdOrControl+Q"))
}
pub fn update_systray(app_handle: &AppHandle) -> Result<()> {
@@ -71,6 +100,20 @@ impl Tray {
}
pub fn update_part(app_handle: &AppHandle) -> Result<()> {
let zh = { Config::verge().latest().language == Some("zh".into()) };
let version = app_handle.package_info().version.to_string();
macro_rules! t {
($en: expr, $zh: expr) => {
if zh {
$zh
} else {
$en
}
};
}
let mode = {
Config::clash()
.latest()
@@ -86,44 +129,196 @@ impl Tray {
let _ = tray.get_item("rule_mode").set_selected(mode == "rule");
let _ = tray.get_item("global_mode").set_selected(mode == "global");
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
let _ = tray.get_item("script_mode").set_selected(mode == "script");
#[cfg(target_os = "linux")]
match mode.as_str() {
"rule" => {
let _ = tray
.get_item("rule_mode")
.set_title(t!("Rule Mode ✔", "规则模式 ✔"));
let _ = tray
.get_item("global_mode")
.set_title(t!("Global Mode", "全局模式"));
let _ = tray
.get_item("direct_mode")
.set_title(t!("Direct Mode", "直连模式"));
}
"global" => {
let _ = tray
.get_item("rule_mode")
.set_title(t!("Rule Mode", "规则模式"));
let _ = tray
.get_item("global_mode")
.set_title(t!("Global Mode ✔", "全局模式 ✔"));
let _ = tray
.get_item("direct_mode")
.set_title(t!("Direct Mode", "直连模式"));
}
"direct" => {
let _ = tray
.get_item("rule_mode")
.set_title(t!("Rule Mode", "规则模式"));
let _ = tray
.get_item("global_mode")
.set_title(t!("Global Mode", "全局模式"));
let _ = tray
.get_item("direct_mode")
.set_title(t!("Direct Mode ✔", "直连模式 ✔"));
}
_ => {}
}
let verge = Config::verge();
let verge = verge.latest();
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 common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
let 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")]
let mut icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
if *sysproxy_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("sysproxy.png");
let ico_path = icon_dir_path.join("sysproxy.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
} else {
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../icons/tray-icon.png").to_vec();
#[cfg(target_os = "macos")]
let mut icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
if *common_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("common.png");
let ico_path = icon_dir_path.join("common.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
};
if *tun_mode {
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../icons/tray-icon-tun.png").to_vec();
#[cfg(target_os = "macos")]
let mut icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
if *tun_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("tun.png");
let ico_path = icon_dir_path.join("tun.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
indication_icon = icon
}
let _ = tray.set_icon(tauri::Icon::Raw(indication_icon));
let _ = tray.get_item("system_proxy").set_selected(*system_proxy);
let _ = tray.get_item("tun_mode").set_selected(*tun_mode);
#[cfg(target_os = "linux")]
{
if *system_proxy {
let _ = tray
.get_item("system_proxy")
.set_title(t!("System Proxy ✔", "系统代理 ✔"));
} else {
let _ = tray
.get_item("system_proxy")
.set_title(t!("System Proxy", "系统代理"));
}
if *tun_mode {
let _ = tray
.get_item("tun_mode")
.set_title(t!("TUN Mode ✔", "Tun 模式 ✔"));
} else {
let _ = tray
.get_item("tun_mode")
.set_title(t!("TUN Mode", "Tun 模式"));
}
}
let switch_map = {
let mut map = std::collections::HashMap::new();
map.insert(true, "on");
map.insert(false, "off");
map
};
let mut current_profile_name = "None".to_string();
let profiles = Config::profiles();
let profiles = profiles.latest();
if let Some(current_profile_uid) = profiles.get_current() {
let current_profile = profiles.get_item(&current_profile_uid);
current_profile_name = match &current_profile.unwrap().name {
Some(profile_name) => profile_name.to_string(),
None => current_profile_name,
};
};
let _ = tray.set_tooltip(&format!(
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
t!("System Proxy", "系统代理"),
switch_map[system_proxy],
t!("TUN Mode", "Tun 模式"),
switch_map[tun_mode],
t!("Curent Profile", "当前订阅"),
current_profile_name
));
Ok(())
}
pub fn on_click(app_handle: &AppHandle) {
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event = tray_event.unwrap_or("main_window".into());
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(),
"main_window" => resolve::create_window(app_handle),
_ => {}
}
}
pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) {
match event {
#[cfg(not(target_os = "macos"))]
SystemTrayEvent::LeftClick { .. } => Tray::on_click(app_handle),
#[cfg(target_os = "macos")]
SystemTrayEvent::RightClick { .. } => Tray::on_click(app_handle),
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => {
mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
let mode = &mode[0..mode.len() - 5];
feat::change_clash_mode(mode.into());
}
"open_window" => resolve::create_window(app_handle),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(),
"copy_env" => feat::copy_clash_env(app_handle),
"open_app_dir" => crate::log_err!(cmds::open_app_dir()),
"open_core_dir" => crate::log_err!(cmds::open_core_dir()),
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
"restart_clash" => feat::restart_clash_core(),
"restart_app" => api::process::restart(&app_handle.env()),
"quit" => {
resolve::resolve_reset();
api::process::kill_children();
app_handle.exit(0);
std::process::exit(0);
}
"quit" => cmds::exit_app(app_handle.clone()),
_ => {}
},
#[cfg(target_os = "windows")]
SystemTrayEvent::LeftClick { .. } => {
resolve::create_window(app_handle);
}
_ => {}
}
}
+26
View File
@@ -0,0 +1,26 @@
#![cfg(target_os = "windows")]
use crate::utils::dirs;
use anyhow::{bail, Result};
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::process::Command as StdCommand;
pub async fn invoke_uwptools() -> Result<()> {
let resource_dir = dirs::app_resources_dir()?;
let tool_path = resource_dir.join("enableLoopback.exe");
if !tool_path.exists() {
bail!("enableLoopback exe not found");
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(tool_path).status()?,
_ => StdCommand::new(tool_path).status()?,
};
Ok(())
}
+4 -4
View File
@@ -1,6 +1,6 @@
function main(params) {
if (params.mode === "script") {
params.mode = "rule";
function main(config) {
if (config.mode === "script") {
config.mode = "rule";
}
return params;
return config;
}
@@ -1,10 +1,10 @@
function main(params) {
if (Array.isArray(params.proxies)) {
params.proxies.forEach((p, i) => {
function main(config) {
if (Array.isArray(config.proxies)) {
config.proxies.forEach((p, i) => {
if (p.type === "hysteria" && typeof p.alpn === "string") {
params.proxies[i].alpn = [p.alpn];
config.proxies[i].alpn = [p.alpn];
}
});
}
return params;
return config;
}
+12
View File
@@ -21,6 +21,7 @@ pub enum ChainType {
pub enum ChainSupport {
Clash,
ClashMeta,
ClashMetaAlpha,
All,
}
@@ -60,9 +61,19 @@ impl ChainItem {
let hy_alpn =
ChainItem::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
// meta 的一些处理
let meta_guard_alpha =
ChainItem::to_script("verge_meta_guard", include_str!("./builtin/meta_guard.js"));
// meta 1.13.2 alpn string 转 数组
let hy_alpn_alpha =
ChainItem::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
vec![
(ChainSupport::ClashMeta, hy_alpn),
(ChainSupport::ClashMeta, meta_guard),
(ChainSupport::ClashMetaAlpha, hy_alpn_alpha),
(ChainSupport::ClashMetaAlpha, meta_guard_alpha),
]
}
@@ -81,6 +92,7 @@ impl ChainSupport {
(ChainSupport::All, _) => true,
(ChainSupport::Clash, "clash") => true,
(ChainSupport::ClashMeta, "clash-meta") => true,
(ChainSupport::ClashMetaAlpha, "clash-meta-alpha") => true,
_ => false,
},
None => true,
+39 -63
View File
@@ -1,69 +1,28 @@
use serde_yaml::{Mapping, Value};
use std::collections::HashSet;
pub const HANDLE_FIELDS: [&str; 9] = [
"port",
"socks-port",
"mixed-port",
pub const HANDLE_FIELDS: [&str; 11] = [
"mode",
"ipv6",
"log-level",
"redir-port",
"tproxy-port",
"mixed-port",
"socks-port",
"port",
"allow-lan",
"external-controller",
"log-level",
"ipv6",
"secret",
"external-controller",
];
pub const DEFAULT_FIELDS: [&str; 5] = [
"proxies",
"proxy-groups",
"rules",
"proxy-providers",
"proxy-groups",
"rule-providers",
"rules",
];
pub const OTHERS_FIELDS: [&str; 21] = [
"tun",
"dns",
"ebpf",
"hosts",
"script",
"profile",
"payload",
"auto-redir",
"experimental",
"interface-name",
"routing-mark",
"redir-port",
"tproxy-port",
"iptables",
"external-ui",
"bind-address",
"authentication",
"sniffer", // meta
"sub-rules", // meta
"geodata-mode", // meta
"tcp-concurrent", // meta
];
pub fn use_clash_fields() -> Vec<String> {
DEFAULT_FIELDS
.into_iter()
.chain(HANDLE_FIELDS)
.chain(OTHERS_FIELDS)
.map(|s| s.to_string())
.collect()
}
pub fn use_valid_fields(mut valid: Vec<String>) -> Vec<String> {
let others = Vec::from(OTHERS_FIELDS);
valid.iter_mut().for_each(|s| s.make_ascii_lowercase());
valid
.into_iter()
.filter(|s| others.contains(&s.as_str()))
.chain(DEFAULT_FIELDS.iter().map(|s| s.to_string()))
.collect()
}
pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
let mut ret = Mapping::new();
@@ -92,17 +51,34 @@ pub fn use_lowercase(config: Mapping) -> Mapping {
pub fn use_sort(config: Mapping) -> Mapping {
let mut ret = Mapping::new();
HANDLE_FIELDS.into_iter().for_each(|key| {
let key = Value::from(key);
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
}
});
HANDLE_FIELDS
let supported_keys: HashSet<&str> = HANDLE_FIELDS.into_iter().chain(DEFAULT_FIELDS).collect();
let config_keys: HashSet<&str> = config
.keys()
.filter_map(|e| e.as_str())
.into_iter()
.chain(OTHERS_FIELDS)
.chain(DEFAULT_FIELDS)
.for_each(|key| {
let key = Value::from(key);
config.get(&key).map(|value| {
ret.insert(key, value.clone());
});
});
.collect();
config_keys.difference(&supported_keys).for_each(|&key| {
let key = Value::from(key);
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
}
});
DEFAULT_FIELDS.into_iter().for_each(|key| {
let key = Value::from(key);
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
}
});
ret
}
@@ -113,7 +89,7 @@ pub fn use_keys(config: &Mapping) -> Vec<String> {
.map(|s| {
let mut s = s.to_string();
s.make_ascii_lowercase();
return s;
s
})
.collect()
}
+42 -3
View File
@@ -1,12 +1,15 @@
use super::{use_filter, use_lowercase};
use serde_yaml::{self, Mapping, Sequence, Value};
#[allow(unused)]
const MERGE_FIELDS: [&str; 6] = [
const MERGE_FIELDS: [&str; 10] = [
"prepend-rules",
"append-rules",
"prepend-rule-providers",
"append-rule-providers",
"prepend-proxies",
"append-proxies",
"prepend-proxy-providers",
"append-proxy-providers",
"prepend-proxy-groups",
"append-proxy-groups",
];
@@ -15,6 +18,7 @@ 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);
});
@@ -22,6 +26,39 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
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;
}
}
if let Some(post_val) = merge.get(&post_key) {
if post_val.is_mapping() {
map.extend(post_val.as_mapping().unwrap().clone());
}
}
if !map.is_empty() {
config.insert(key_val, Value::from(map));
}
});
["rules", "proxies", "proxy-groups"]
.iter()
.for_each(|key_str| {
@@ -49,7 +86,9 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
}
}
config.insert(key_val, Value::from(list));
if !list.is_empty() {
config.insert(key_val, Value::from(list));
}
});
config
}
+28 -27
View File
@@ -1,12 +1,11 @@
mod chain;
mod field;
pub mod field;
mod merge;
mod script;
mod tun;
pub(self) use self::field::*;
use self::chain::*;
use self::field::*;
use self::merge::*;
use self::script::*;
use self::tun::*;
@@ -18,54 +17,48 @@ use std::collections::HashSet;
type ResultLog = Vec<(String, String)>;
/// Enhance mode
/// 返回最终配置、该配置包含的键、和script执行的结果
/// 返回最终订阅、该订阅包含的键、和script执行的结果
pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// config.yaml 的配置
// config.yaml 的订阅
let clash_config = { Config::clash().latest().0.clone() };
let (clash_core, tun_mode, enable_builtin) = {
let (clash_core, enable_tun, enable_builtin) = {
let verge = Config::verge();
let verge = verge.latest();
(
verge.clash_core.clone(),
verge.enable_tun_mode.clone(),
verge.enable_builtin_enhanced.clone(),
verge.enable_tun_mode.unwrap_or(false),
verge.enable_builtin_enhanced.unwrap_or(true),
)
};
// 从profiles里拿东西
let (mut config, chain, valid) = {
let (mut config, chain) = {
let profiles = Config::profiles();
let profiles = profiles.latest();
let current = profiles.current_mapping().unwrap_or(Mapping::new());
let current = profiles.current_mapping().unwrap_or_default();
let chain = match profiles.chain.as_ref() {
Some(chain) => chain
.iter()
.filter_map(|uid| profiles.get_item(uid).ok())
.filter_map(|item| <Option<ChainItem>>::from(item))
.filter_map(<Option<ChainItem>>::from)
.collect::<Vec<ChainItem>>(),
None => vec![],
};
let valid = profiles.valid.clone().unwrap_or(vec![]);
(current, chain, valid)
(current, chain)
};
let mut result_map = HashMap::new(); // 保存脚本日志
let mut exists_keys = use_keys(&config); // 保存出现过的keys
let valid = use_valid_fields(valid);
config = use_filter(config, &valid);
// 处理用户的profile
chain.into_iter().for_each(|item| match item.data {
ChainType::Merge(merge) => {
exists_keys.extend(use_keys(&merge));
config = use_merge(merge, config.to_owned());
config = use_filter(config.to_owned(), &valid);
}
ChainType::Script(script) => {
let mut logs = vec![];
@@ -73,7 +66,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
match use_script(script, config.to_owned()) {
Ok((res_config, res_logs)) => {
exists_keys.extend(use_keys(&res_config));
config = use_filter(res_config, &valid);
config = res_config;
logs.extend(res_logs);
}
Err(err) => logs.push(("exception".into(), err.to_string())),
@@ -85,13 +78,22 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// 合并默认的config
for (key, value) in clash_config.into_iter() {
config.insert(key, value);
if key.as_str() == Some("tun") {
let mut tun = config.get_mut("tun").map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new());
for (key, value) in patch_tun.into_iter() {
tun.insert(key, value);
}
config.insert("tun".into(), tun.into());
} else {
config.insert(key, value);
}
}
let clash_fields = use_clash_fields();
// 内建脚本最后跑
if enable_builtin.unwrap_or(true) {
if enable_builtin {
ChainItem::builtin()
.into_iter()
.filter(|(s, _)| s.is_support(clash_core.as_ref()))
@@ -102,7 +104,7 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
match item.data {
ChainType::Script(script) => match use_script(script, config.to_owned()) {
Ok((res_config, _)) => {
config = use_filter(res_config, &clash_fields);
config = res_config;
}
Err(err) => {
log::error!(target: "app", "builtin script error `{err}`");
@@ -113,12 +115,11 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
});
}
config = use_filter(config, &clash_fields);
config = use_tun(config, tun_mode.unwrap_or(false));
config = use_tun(config, enable_tun);
config = use_sort(config);
let mut exists_set = HashSet::new();
exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
exists_set.extend(exists_keys.into_iter());
exists_keys = exists_set.into_iter().collect();
(config, exists_keys, result_map)
+44 -31
View File
@@ -1,62 +1,75 @@
use super::use_lowercase;
use anyhow::Result;
use anyhow::{Error, Result};
use serde_yaml::Mapping;
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
use rquickjs::{Context, Func, Runtime};
use boa_engine::{native_function::NativeFunction, Context, JsValue, Source};
use std::sync::{Arc, Mutex};
let mut context = Context::default();
let runtime = Runtime::new().unwrap();
let context = Context::full(&runtime).unwrap();
let outputs = Arc::new(Mutex::new(vec![]));
let copy_outputs = outputs.clone();
let result = context.with(|ctx| -> Result<Mapping> {
ctx.globals().set(
"__verge_log__",
Func::from(move |level: String, data: String| {
let mut out = copy_outputs.lock().unwrap();
out.push((level, data));
}),
)?;
ctx.eval(
r#"var console = Object.freeze({
unsafe {
let _ = context.register_global_builtin_callable(
"__verge_log__".into(),
2,
NativeFunction::from_closure(
move |_: &JsValue, args: &[JsValue], context: &mut Context| {
let level = args.get(0).unwrap().to_string(context)?;
let level = level.to_std_string().unwrap();
let data = args.get(1).unwrap().to_string(context)?;
let data = data.to_std_string().unwrap();
let mut out = copy_outputs.lock().unwrap();
out.push((level, data));
Ok(JsValue::undefined())
},
),
);
}
let _ = context.eval(Source::from_bytes(
r#"var console = Object.freeze({
log(data){__verge_log__("log",JSON.stringify(data))},
info(data){__verge_log__("info",JSON.stringify(data))},
error(data){__verge_log__("error",JSON.stringify(data))},
debug(data){__verge_log__("debug",JSON.stringify(data))},
});"#,
)?;
));
let config = use_lowercase(config.clone());
let config_str = serde_json::to_string(&config)?;
let config = use_lowercase(config.clone());
let config_str = serde_json::to_string(&config)?;
let code = format!(
r#"try{{
let code = format!(
r#"try{{
{script};
JSON.stringify(main({config_str})||'')
}} catch(err) {{
`__error_flag__ ${{err.toString()}}`
}}"#
);
let result: String = ctx.eval(code.as_str())?;
);
if let Ok(result) = context.eval(Source::from_bytes(code.as_str())) {
if !result.is_string() {
anyhow::bail!("main function should return object");
}
let result = result.to_string(&mut context).unwrap();
let result = result.to_std_string().unwrap();
if result.starts_with("__error_flag__") {
anyhow::bail!(result[15..].to_owned());
}
if result == "\"\"" {
anyhow::bail!("main function should return object");
}
return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
});
let mut out = outputs.lock().unwrap();
match result {
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
Err(err) => {
out.push(("exception".into(), err.to_string()));
Ok((config, out.to_vec()))
let res: Result<Mapping, Error> = Ok(serde_json::from_str::<Mapping>(result.as_str())?);
let mut out = outputs.lock().unwrap();
match res {
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
Err(err) => {
out.push(("exception".into(), err.to_string()));
Ok((config, out.to_vec()))
}
}
} else {
anyhow::bail!("main function should return object");
}
}
+52 -6
View File
@@ -30,18 +30,64 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
});
revise!(tun_val, "enable", enable);
if enable {
append!(tun_val, "stack", "gvisor");
append!(tun_val, "dns-hijack", vec!["any:53"]);
append!(tun_val, "auto-route", true);
append!(tun_val, "auto-detect-interface", true);
}
revise!(config, "tun", tun_val);
if enable {
#[cfg(target_os = "macos")]
{
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)
} else {
#[cfg(target_os = "macos")]
{
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
}
}
+133 -88
View File
@@ -7,8 +7,25 @@
use crate::config::*;
use crate::core::*;
use crate::log_err;
use crate::utils::resolve;
use anyhow::{bail, Result};
use serde_yaml::{Mapping, Value};
use tauri::{AppHandle, ClipboardManager, Manager};
// 打开面板
pub fn open_or_close_dashboard() {
let handle = handle::Handle::global();
let app_handle = handle.app_handle.lock();
if let Some(app_handle) = app_handle.as_ref() {
if let Some(window) = app_handle.get_window("main") {
if let Ok(true) = window.is_focused() {
let _ = window.close();
return;
}
}
resolve::create_window(app_handle);
}
}
// 重启clash
pub fn restart_clash_core() {
@@ -36,7 +53,7 @@ pub fn change_clash_mode(mode: String) {
match clash_api::patch_configs(&mapping).await {
Ok(_) => {
// 更新配置
// 更新订阅
Config::clash().data().patch_config(mapping);
if Config::clash().data().save_config().is_ok() {
@@ -51,7 +68,7 @@ pub fn change_clash_mode(mode: String) {
// 切换系统代理
pub fn toggle_system_proxy() {
let enable = Config::verge().draft().enable_system_proxy.clone();
let enable = Config::verge().draft().enable_system_proxy;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
@@ -67,39 +84,9 @@ pub fn toggle_system_proxy() {
});
}
// 打开系统代理
pub fn enable_system_proxy() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_system_proxy: Some(true),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 关闭系统代理
pub fn disable_system_proxy() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_system_proxy: Some(false),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 切换tun模式
pub fn toggle_tun_mode() {
let enable = Config::verge().data().enable_tun_mode.clone();
let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
@@ -115,47 +102,26 @@ pub fn toggle_tun_mode() {
});
}
// 打开tun模式
pub fn enable_tun_mode() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_tun_mode: Some(true),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
// 关闭tun模式
pub fn disable_tun_mode() {
tauri::async_runtime::spawn(async {
match patch_verge(IVerge {
enable_tun_mode: Some(false),
..IVerge::default()
})
.await
{
Ok(_) => handle::Handle::refresh_verge(),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}
/// 修改clash的配置
/// 修改clash的订阅
pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::clash().draft().patch_config(patch.clone());
match {
let redir_port = patch.get("redir-port");
let tproxy_port = patch.get("tproxy-port");
let mixed_port = patch.get("mixed-port");
if mixed_port.is_some() {
let changed = mixed_port != Config::clash().data().0.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.clone().unwrap().as_u64() {
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");
@@ -164,8 +130,12 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
}
};
// 激活配置
if mixed_port.is_some()
// 激活订阅
if redir_port.is_some()
|| tproxy_port.is_some()
|| mixed_port.is_some()
|| socks_port.is_some()
|| port.is_some()
|| patch.get("secret").is_some()
|| patch.get("external-controller").is_some()
{
@@ -183,6 +153,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
log_err!(handle::Handle::update_systray_part());
}
Config::runtime().latest().patch_config(patch);
<Result<()>>::Ok(())
} {
Ok(()) => {
@@ -197,7 +169,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
}
}
/// 修改verge的配置
/// 修改verge的订阅
/// 一般都是一个个的修改
pub async fn patch_verge(patch: IVerge) -> Result<()> {
Config::verge().draft().patch_config(patch.clone());
@@ -207,31 +179,27 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
let system_proxy = patch.enable_system_proxy;
let proxy_bypass = patch.system_proxy_bypass;
let language = patch.language;
let port = patch.verge_mixed_port;
let common_tray_icon = patch.common_tray_icon;
let sysproxy_tray_icon = patch.sysproxy_tray_icon;
let tun_tray_icon = patch.tun_tray_icon;
match {
#[cfg(target_os = "windows")]
{
let service_mode = patch.enable_service_mode;
let service_mode = patch.enable_service_mode;
if service_mode.is_some() {
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
if service_mode.is_some() {
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
Config::generate()?;
CoreManager::global().run_core().await?;
} else if tun_mode.is_some() {
update_core_config().await?;
}
}
#[cfg(not(target_os = "windows"))]
if tun_mode.is_some() {
Config::generate()?;
CoreManager::global().run_core().await?;
} else if tun_mode.is_some() {
update_core_config().await?;
}
if auto_launch.is_some() {
sysopt::Sysopt::global().update_launch()?;
}
if system_proxy.is_some() || proxy_bypass.is_some() {
if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() {
sysopt::Sysopt::global().update_sysproxy()?;
sysopt::Sysopt::global().guard_proxy();
}
@@ -246,7 +214,12 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
if language.is_some() {
handle::Handle::update_systray()?;
} else if system_proxy.or(tun_mode).is_some() {
} else if system_proxy.is_some()
|| tun_mode.is_some()
|| common_tray_icon.is_some()
|| sysproxy_tray_icon.is_some()
|| tun_tray_icon.is_some()
{
handle::Handle::update_systray_part()?;
}
@@ -265,7 +238,7 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
}
/// 更新某个profile
/// 如果更新当前配置就激活配置
/// 如果更新当前订阅就激活订阅
pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
let url_opt = {
let profiles = Config::profiles();
@@ -303,7 +276,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
Ok(())
}
/// 更新配置
/// 更新订阅
async fn update_core_config() -> Result<()> {
match CoreManager::global().update_config().await {
Ok(_) => {
@@ -317,3 +290,75 @@ async fn update_core_config() -> Result<()> {
}
}
}
/// copy env variable
pub fn copy_clash_env(app_handle: &AppHandle) {
let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) };
let http_proxy = format!("http://127.0.0.1:{}", port);
let socks5_proxy = format!("socks5://127.0.0.1:{}", port);
let sh =
format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
let mut cliboard = app_handle.clipboard_manager();
let env_type = { Config::verge().latest().env_type.clone() };
let env_type = match env_type {
Some(env_type) => env_type,
None => {
#[cfg(not(target_os = "windows"))]
let default = "bash";
#[cfg(target_os = "windows")]
let default = "powershell";
default.to_string()
}
};
match env_type.as_str() {
"bash" => cliboard.write_text(sh).unwrap_or_default(),
"cmd" => cliboard.write_text(cmd).unwrap_or_default(),
"powershell" => cliboard.write_text(ps).unwrap_or_default(),
_ => log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"),
};
}
pub async fn test_delay(url: String) -> Result<u32> {
use tokio::time::{Duration, Instant};
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false);
let proxy_scheme = format!("http://127.0.0.1:{port}");
if !tun_mode {
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
let request = builder
.timeout(Duration::from_millis(10000))
.build()?
.get(url).header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");
let start = Instant::now();
let response = request.send().await?;
if response.status().is_success() {
let delay = start.elapsed().as_millis() as u32;
Ok(delay)
} else {
Ok(10000u32)
}
}
+29 -11
View File
@@ -20,12 +20,18 @@ fn main() -> std::io::Result<()> {
return Ok(());
}
#[cfg(target_os = "linux")]
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
crate::log_err!(init::init_config());
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.system_tray(SystemTray::new())
.setup(|app| Ok(resolve::resolve_setup(app)))
.setup(|app| {
resolve::resolve_setup(app);
Ok(())
})
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
.invoke_handler(tauri::generate_handler![
// common
@@ -33,8 +39,11 @@ fn main() -> std::io::Result<()> {
cmds::open_app_dir,
cmds::open_logs_dir,
cmds::open_web_url,
cmds::open_core_dir,
cmds::get_portable_flag,
// cmds::kill_sidecar,
cmds::restart_sidecar,
cmds::grant_permission,
// clash
cmds::get_clash_info,
cmds::get_clash_logs,
@@ -44,9 +53,16 @@ fn main() -> std::io::Result<()> {
cmds::get_runtime_yaml,
cmds::get_runtime_exists,
cmds::get_runtime_logs,
cmds::uwp::invoke_uwp_tool,
// verge
cmds::get_verge_config,
cmds::patch_verge_config,
cmds::test_delay,
cmds::get_app_dir,
cmds::copy_icon_file,
cmds::download_icon_cache,
cmds::open_devtools,
cmds::exit_app,
// cmds::update_hotkeys,
// profile
cmds::get_profiles,
@@ -56,6 +72,7 @@ fn main() -> std::io::Result<()> {
cmds::patch_profile,
cmds::create_profile,
cmds::import_profile,
cmds::reorder_profile,
cmds::update_profile,
cmds::delete_profile,
cmds::read_profile_file,
@@ -64,6 +81,8 @@ fn main() -> std::io::Result<()> {
cmds::service::check_service,
cmds::service::install_service,
cmds::service::uninstall_service,
// clash api
cmds::clash_api_get_proxy_delay
]);
#[cfg(target_os = "macos")]
@@ -94,22 +113,21 @@ fn main() -> std::io::Result<()> {
tauri::RunEvent::ExitRequested { api, .. } => {
api.prevent_exit();
}
tauri::RunEvent::Exit => {
tauri::RunEvent::Updater(tauri::UpdaterEvent::Downloaded) => {
resolve::resolve_reset();
api::process::kill_children();
app_handle.exit(0);
}
#[cfg(target_os = "macos")]
tauri::RunEvent::WindowEvent { label, event, .. } => {
use tauri::Manager;
if label == "main" {
match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
app_handle.get_window("main").map(|win| {
let _ = win.hide();
});
tauri::WindowEvent::Destroyed => {
let _ = resolve::save_window_size_position(app_handle, true);
}
tauri::WindowEvent::CloseRequested { .. } => {
let _ = resolve::save_window_size_position(app_handle, true);
}
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
let _ = resolve::save_window_size_position(app_handle, false);
}
_ => {}
}
+42 -75
View File
@@ -1,88 +1,69 @@
use crate::core::handle;
use anyhow::Result;
use once_cell::sync::OnceCell;
use std::path::PathBuf;
use tauri::{
api::path::{home_dir, resource_dir},
Env, PackageInfo,
api::path::{data_dir, resource_dir},
Env,
};
#[cfg(not(feature = "verge-dev"))]
static APP_DIR: &str = "clash-verge";
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
#[cfg(feature = "verge-dev")]
static APP_DIR: &str = "clash-verge-dev";
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
static CLASH_CONFIG: &str = "config.yaml";
static VERGE_CONFIG: &str = "verge.yaml";
static PROFILE_YAML: &str = "profiles.yaml";
static mut RESOURCE_DIR: Option<PathBuf> = None;
/// portable flag
#[allow(unused)]
static mut PORTABLE_FLAG: bool = false;
pub static mut APP_VERSION: &str = "v1.1.2";
/// initialize portable flag
#[cfg(target_os = "windows")]
pub unsafe fn init_portable_flag() -> Result<()> {
/// init portable flag
pub fn init_portable_flag() -> Result<()> {
use tauri::utils::platform::current_exe;
let exe = current_exe()?;
if let Some(dir) = exe.parent() {
let app_exe = current_exe()?;
if let Some(dir) = app_exe.parent() {
let dir = PathBuf::from(dir).join(".config/PORTABLE");
if dir.exists() {
PORTABLE_FLAG = true;
PORTABLE_FLAG.get_or_init(|| true);
}
}
PORTABLE_FLAG.get_or_init(|| false);
Ok(())
}
/// get the verge app home dir
pub fn app_home_dir() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
unsafe {
use tauri::utils::platform::current_exe;
use tauri::utils::platform::current_exe;
if !PORTABLE_FLAG {
Ok(home_dir()
.ok_or(anyhow::anyhow!("failed to get app home dir"))?
.join(".config")
.join(APP_DIR))
} else {
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_dir = app_exe
.parent()
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
Ok(PathBuf::from(app_dir).join(".config").join(APP_DIR))
}
let flag = PORTABLE_FLAG.get().unwrap_or(&false);
if *flag {
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_dir = app_exe
.parent()
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
}
#[cfg(not(target_os = "windows"))]
Ok(home_dir()
.ok_or(anyhow::anyhow!("failed to get the app home dir"))?
.join(".config")
.join(APP_DIR))
Ok(data_dir()
.ok_or(anyhow::anyhow!("failed to get app home dir"))?
.join(APP_ID))
}
/// get the resources dir
pub fn app_resources_dir(package_info: &PackageInfo) -> Result<PathBuf> {
let res_dir = resource_dir(package_info, &Env::default())
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?
.join("resources");
unsafe {
RESOURCE_DIR = Some(res_dir.clone());
let ver = package_info.version.to_string();
let ver_str = format!("v{ver}");
APP_VERSION = Box::leak(Box::new(ver_str));
}
Ok(res_dir)
pub fn app_resources_dir() -> Result<PathBuf> {
let handle = handle::Handle::global();
let app_handle = handle.app_handle.lock();
if let Some(app_handle) = app_handle.as_ref() {
let res_dir = resource_dir(app_handle.package_info(), &Env::default())
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?
.join("resources");
return Ok(res_dir);
};
Err(anyhow::anyhow!("failed to get the resource dir"))
}
/// profiles dir
@@ -107,34 +88,20 @@ pub fn profiles_path() -> Result<PathBuf> {
Ok(app_home_dir()?.join(PROFILE_YAML))
}
pub fn app_res_dir() -> Result<PathBuf> {
unsafe {
Ok(RESOURCE_DIR
.clone()
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?)
}
pub fn clash_pid_path() -> Result<PathBuf> {
Ok(app_home_dir()?.join("clash.pid"))
}
pub fn clash_pid_path() -> Result<PathBuf> {
unsafe {
Ok(RESOURCE_DIR
.clone()
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?
.join("clash.pid"))
}
#[cfg(not(target_os = "windows"))]
pub fn service_path() -> Result<PathBuf> {
Ok(app_resources_dir()?.join("clash-verge-service"))
}
#[cfg(windows)]
pub fn service_path() -> Result<PathBuf> {
unsafe {
let res_dir = RESOURCE_DIR
.clone()
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?;
Ok(res_dir.join("clash-verge-service.exe"))
}
Ok(app_resources_dir()?.join("clash-verge-service.exe"))
}
#[cfg(windows)]
pub fn service_log_file() -> Result<PathBuf> {
use chrono::Local;
+64 -47
View File
@@ -2,7 +2,11 @@ use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::{Mapping, Value};
use std::{fs, path::PathBuf, process::Command, str::FromStr};
use std::{fs, path::PathBuf, str::FromStr};
use tauri::{
api::shell::{open, Program},
Manager,
};
/// read data from yaml as struct T
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
@@ -10,20 +14,22 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
bail!("file not found \"{}\"", path.display());
}
let yaml_str = fs::read_to_string(&path)
.context(format!("failed to read the file \"{}\"", path.display()))?;
let yaml_str = fs::read_to_string(path)
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
serde_yaml::from_str::<T>(&yaml_str).context(format!(
"failed to read the file with yaml format \"{}\"",
path.display()
))
serde_yaml::from_str::<T>(&yaml_str).with_context(|| {
format!(
"failed to read the file with yaml format \"{}\"",
path.display()
)
})
}
/// read mapping from yaml fix #165
pub fn read_merge_mapping(path: &PathBuf) -> Result<Mapping> {
let mut val: Value = read_yaml(path)?;
val.apply_merge()
.context(format!("failed to apply merge \"{}\"", path.display()))?;
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
Ok(val
.as_mapping()
@@ -45,7 +51,8 @@ pub fn save_yaml<T: Serialize>(path: &PathBuf, data: &T, prefix: Option<&str>) -
};
let path_str = path.as_os_str().to_string_lossy().to_string();
fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\""))
fs::write(path, yaml_str.as_bytes())
.with_context(|| format!("failed to save file \"{path_str}\""))
}
const ALPHABET: [char; 62] = [
@@ -64,44 +71,45 @@ pub fn get_uid(prefix: &str) -> String {
/// parse the string
/// xxx=123123; => 123123
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
target.find(key).and_then(|idx| {
let idx = idx + key.len();
let value = &target[idx..];
match value.split(';').nth(0) {
Some(value) => value.trim().parse(),
None => value.trim().parse(),
target.split(';').map(str::trim).find_map(|s| {
let mut parts = s.splitn(2, '=');
match (parts.next(), parts.next()) {
(Some(k), Some(v)) if k == key => v.parse::<T>().ok(),
_ => None,
}
.ok()
})
}
/// get the last part of the url, if not found, return empty string
pub fn get_last_part_and_decode(url: &str) -> Option<String> {
let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part
let segments: Vec<&str> = path.split('/').collect();
let last_segment = segments.last()?;
Some(
percent_encoding::percent_decode_str(last_segment)
.decode_utf8_lossy()
.to_string(),
)
}
/// open file
/// use vscode by default
pub fn open_file(path: PathBuf) -> Result<()> {
// use vscode first
if let Ok(code) = which::which("code") {
let mut command = Command::new(&code);
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
#[cfg(target_os = "macos")]
let code = "Visual Studio Code";
#[cfg(not(target_os = "macos"))]
let code = "code";
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
if let Err(err) = command.creation_flags(0x08000000).arg(&path).spawn() {
log::error!(target: "app", "failed to open with VScode `{err}`");
open::that(path)?;
}
let _ = match Program::from_str(code) {
Ok(code) => open(&app.shell_scope(), path.to_string_lossy(), Some(code)),
Err(err) => {
log::error!(target: "app", "Can't find VScode `{err}`");
// default open
open(&app.shell_scope(), path.to_string_lossy(), None)
}
};
#[cfg(not(target_os = "windows"))]
if let Err(err) = command.arg(&path).spawn() {
log::error!(target: "app", "failed to open with VScode `{err}`");
open::that(path)?;
}
return Ok(());
}
open::that(path)?;
Ok(())
}
@@ -127,6 +135,15 @@ macro_rules! log_err {
};
}
#[macro_export]
macro_rules! trace_err {
($result: expr, $err_str: expr) => {
if let Err(err) = $result {
log::trace!(target: "app", "{}, err {}", $err_str, err);
}
}
}
/// wrap the anyhow error
/// transform the error to String
#[macro_export]
@@ -155,17 +172,17 @@ fn test_parse_value() {
let test_1 = "upload=111; download=2222; total=3333; expire=444";
let test_2 = "attachment; filename=Clash.yaml";
assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
assert_eq!(parse_str::<usize>(test_1, "upload").unwrap(), 111);
assert_eq!(parse_str::<usize>(test_1, "download").unwrap(), 2222);
assert_eq!(parse_str::<usize>(test_1, "total").unwrap(), 3333);
assert_eq!(parse_str::<usize>(test_1, "expire").unwrap(), 444);
assert_eq!(
parse_str::<String>(test_2, "filename=").unwrap(),
parse_str::<String>(test_2, "filename").unwrap(),
format!("Clash.yaml")
);
assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
assert_eq!(parse_str::<usize>(test_1, "aaa"), None);
assert_eq!(parse_str::<usize>(test_1, "upload1"), None);
assert_eq!(parse_str::<usize>(test_1, "expire1"), None);
assert_eq!(parse_str::<usize>(test_2, "attachment"), None);
}
+242 -34
View File
@@ -1,14 +1,16 @@
use crate::config::*;
use crate::utils::{dirs, help};
use anyhow::Result;
use chrono::Local;
use chrono::{Local, TimeZone};
use log::LevelFilter;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Config, Logger, Root};
use log4rs::config::{Appender, Logger, Root};
use log4rs::encode::pattern::PatternEncoder;
use std::fs;
use tauri::PackageInfo;
use std::fs::{self, DirEntry};
use std::path::PathBuf;
use std::str::FromStr;
use tauri::api::process::Command;
/// initialize this instance's log file
fn init_log() -> Result<()> {
@@ -17,49 +19,137 @@ fn init_log() -> Result<()> {
let _ = fs::create_dir_all(&log_dir);
}
let log_level = Config::verge().data().get_log_level();
if log_level == LevelFilter::Off {
return Ok(());
}
let local_time = Local::now().format("%Y-%m-%d-%H%M").to_string();
let log_file = format!("{}.log", local_time);
let log_file = log_dir.join(log_file);
#[cfg(feature = "verge-dev")]
let time_format = "{d(%Y-%m-%d %H:%M:%S)} {l} - {M} {m}{n}";
#[cfg(not(feature = "verge-dev"))]
let time_format = "{d(%Y-%m-%d %H:%M:%S)} {l} - {m}{n}";
let log_pattern = match log_level {
LevelFilter::Trace => "{d(%Y-%m-%d %H:%M:%S)} {l} [{M}] - {m}{n}",
_ => "{d(%Y-%m-%d %H:%M:%S)} {l} - {m}{n}",
};
let encode = Box::new(PatternEncoder::new(time_format));
let encode = Box::new(PatternEncoder::new(log_pattern));
let stdout = ConsoleAppender::builder().encoder(encode.clone()).build();
let tofile = FileAppender::builder().encoder(encode).build(log_file)?;
#[cfg(feature = "verge-dev")]
let level = LevelFilter::Debug;
#[cfg(not(feature = "verge-dev"))]
let level = LevelFilter::Info;
let mut logger_builder = Logger::builder();
let mut root_builder = Root::builder();
let config = Config::builder()
let log_more = log_level == LevelFilter::Trace || log_level == LevelFilter::Debug;
#[cfg(feature = "verge-dev")]
{
logger_builder = logger_builder.appenders(["file", "stdout"]);
if log_more {
root_builder = root_builder.appenders(["file", "stdout"]);
} else {
root_builder = root_builder.appenders(["stdout"]);
}
}
#[cfg(not(feature = "verge-dev"))]
{
logger_builder = logger_builder.appenders(["file"]);
if log_more {
root_builder = root_builder.appenders(["file"]);
}
}
let (config, _) = log4rs::config::Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("file", Box::new(tofile)))
.logger(
Logger::builder()
.appenders(["file", "stdout"])
.additive(false)
.build("app", level),
)
.build(Root::builder().appender("stdout").build(LevelFilter::Info))?;
.logger(logger_builder.additive(false).build("app", log_level))
.build_lossy(root_builder.build(log_level));
log4rs::init_config(config)?;
Ok(())
}
/// Initialize all the files from resources
pub fn init_config() -> Result<()> {
#[cfg(target_os = "windows")]
unsafe {
let _ = dirs::init_portable_flag();
/// 删除log文件
pub fn delete_log() -> Result<()> {
let log_dir = dirs::app_logs_dir()?;
if !log_dir.exists() {
return Ok(());
}
let auto_log_clean = {
let verge = Config::verge();
let verge = verge.data();
verge.auto_log_clean.unwrap_or(0)
};
let day = match auto_log_clean {
1 => 7,
2 => 30,
3 => 90,
_ => return Ok(()),
};
log::debug!(target: "app", "try to delete log files, day: {day}");
// %Y-%m-%d to NaiveDateTime
let parse_time_str = |s: &str| {
let sa: Vec<&str> = s.split('-').collect();
if sa.len() != 4 {
return Err(anyhow::anyhow!("invalid time str"));
}
let year = i32::from_str(sa[0])?;
let month = u32::from_str(sa[1])?;
let day = u32::from_str(sa[2])?;
let time = chrono::NaiveDate::from_ymd_opt(year, month, day)
.ok_or(anyhow::anyhow!("invalid time str"))?
.and_hms_opt(0, 0, 0)
.ok_or(anyhow::anyhow!("invalid time str"))?;
Ok(time)
};
let process_file = |file: DirEntry| -> Result<()> {
let file_name = file.file_name();
let file_name = file_name.to_str().unwrap_or_default();
if file_name.ends_with(".log") {
let now = Local::now();
let created_time = parse_time_str(&file_name[0..file_name.len() - 4])?;
let file_time = Local
.from_local_datetime(&created_time)
.single()
.ok_or(anyhow::anyhow!("invalid local datetime"))?;
let duration = now.signed_duration_since(file_time);
if duration.num_days() > day {
let file_path = file.path();
let _ = fs::remove_file(file_path);
log::info!(target: "app", "delete log file: {file_name}");
}
}
Ok(())
};
for file in fs::read_dir(&log_dir)?.flatten() {
let _ = process_file(file);
}
let service_log_dir = log_dir.join("service");
for file in fs::read_dir(&service_log_dir)?.flatten() {
let _ = process_file(file);
}
Ok(())
}
/// Initialize all the config files
/// before tauri setup
pub fn init_config() -> Result<()> {
let _ = dirs::init_portable_flag();
let _ = init_log();
let _ = delete_log();
crate::log_err!(dirs::app_home_dir().map(|app_dir| {
if !app_dir.exists() {
@@ -75,7 +165,7 @@ pub fn init_config() -> Result<()> {
crate::log_err!(dirs::clash_path().map(|path| {
if !path.exists() {
help::save_yaml(&path, &IClashTemp::template().0, Some("# Clash Verge"))?;
help::save_yaml(&path, &IClashTemp::template().0, Some("# Clash Vergeasu"))?;
}
<Result<()>>::Ok(())
}));
@@ -97,10 +187,11 @@ pub fn init_config() -> Result<()> {
Ok(())
}
/// initialize app
pub fn init_resources(package_info: &PackageInfo) -> Result<()> {
/// initialize app resources
/// after tauri setup
pub fn init_resources() -> Result<()> {
let app_dir = dirs::app_home_dir()?;
let res_dir = dirs::app_resources_dir(package_info)?;
let res_dir = dirs::app_resources_dir()?;
if !app_dir.exists() {
let _ = fs::create_dir_all(&app_dir);
@@ -109,14 +200,131 @@ pub fn init_resources(package_info: &PackageInfo) -> Result<()> {
let _ = fs::create_dir_all(&res_dir);
}
#[cfg(target_os = "windows")]
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
#[cfg(not(target_os = "windows"))]
let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
// copy the resource file
for file in ["Country.mmdb", "geoip.dat", "geosite.dat", "wintun.dll"].iter() {
// if the source file is newer than the destination file, copy it over
for file in file_list.iter() {
let src_path = res_dir.join(file);
let target_path = app_dir.join(file);
if src_path.exists() {
let _ = fs::copy(src_path, target_path);
let dest_path = app_dir.join(file);
let handle_copy = || {
match fs::copy(&src_path, &dest_path) {
Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
Err(err) => {
log::error!(target: "app", "failed to copy resources '{file}', {err}")
}
};
};
if src_path.exists() && !dest_path.exists() {
handle_copy();
continue;
}
let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
match (src_modified, dest_modified) {
(Ok(src_modified), Ok(dest_modified)) => {
if src_modified > dest_modified {
handle_copy();
} else {
log::debug!(target: "app", "skipping resource copy '{file}'");
}
}
_ => {
log::debug!(target: "app", "failed to get modified '{file}'");
handle_copy();
}
};
}
Ok(())
}
/// initialize url scheme
#[cfg(target_os = "windows")]
pub fn init_scheme() -> Result<()> {
use tauri::utils::platform::current_exe;
use winreg::enums::*;
use winreg::RegKey;
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_exe = app_exe.to_string_lossy().into_owned();
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
clash.set_value("", &"Clash Verge")?;
clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
default_icon.set_value("", &app_exe)?;
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
command.set_value("", &format!("{app_exe} \"%1\""))?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn init_scheme() -> Result<()> {
let output = std::process::Command::new("xdg-mime")
.arg("default")
.arg("clash-verge.desktop")
.arg("x-scheme-handler/clash")
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"failed to set clash scheme, {}",
String::from_utf8_lossy(&output.stderr)
));
}
Ok(())
}
#[cfg(target_os = "macos")]
pub fn init_scheme() -> Result<()> {
Ok(())
}
pub fn startup_script() -> Result<()> {
let path = {
let verge = Config::verge();
let verge = verge.latest();
verge.startup_script.clone().unwrap_or("".to_string())
};
if !path.is_empty() {
let mut shell = "";
if path.ends_with(".sh") {
shell = "bash";
}
if path.ends_with(".ps1") {
shell = "powershell";
}
if path.ends_with(".bat") {
shell = "powershell";
}
if shell.is_empty() {
return Err(anyhow::anyhow!("unsupported script: {path}"));
}
let current_dir = PathBuf::from(path.clone());
if !current_dir.exists() {
return Err(anyhow::anyhow!("script not found: {path}"));
}
let current_dir = current_dir.parent();
match current_dir {
Some(dir) => {
let _ = Command::new(shell)
.current_dir(dir.to_path_buf())
.args(&[path])
.output()?;
}
None => {
let _ = Command::new(shell).args(&[path]).output()?;
}
}
}
Ok(())
}
+1 -1
View File
@@ -4,4 +4,4 @@ pub mod init;
pub mod resolve;
pub mod server;
pub mod tmpl;
// mod winhelp;
pub mod unix_helper;
+210 -49
View File
@@ -1,27 +1,91 @@
use crate::config::Config;
use crate::log_err;
use crate::{core::*, utils::init, utils::server};
use crate::config::{IVerge, PrfOption};
use crate::{
config::{Config, PrfItem},
core::*,
utils::init,
utils::server,
};
use crate::{log_err, trace_err};
use anyhow::Result;
use once_cell::sync::OnceCell;
use serde_yaml::Mapping;
use std::net::TcpListener;
use tauri::api::notification;
use tauri::{App, AppHandle, Manager};
use window_shadows::set_shadow;
pub static VERSION: OnceCell<String> = OnceCell::new();
pub fn find_unused_port() -> Result<u16> {
match TcpListener::bind("127.0.0.1:0") {
Ok(listener) => {
let port = listener.local_addr()?.port();
Ok(port)
}
Err(_) => {
let port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
log::warn!(target: "app", "use default port: {}", port);
Ok(port)
}
}
}
/// handle something when start app
pub fn resolve_setup(app: &mut App) {
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
let version = app.package_info().version.to_string();
handle::Handle::global().init(app.app_handle());
VERSION.get_or_init(|| version.clone());
log_err!(init::init_resources(app.package_info()));
log_err!(init::init_resources());
log_err!(init::init_scheme());
log_err!(init::startup_script());
// 处理随机端口
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
let mut port = Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
if enable_random_port {
port = find_unused_port().unwrap_or(
Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port()),
);
}
Config::verge().data().patch_config(IVerge {
verge_mixed_port: Some(port),
..IVerge::default()
});
let _ = Config::verge().data().save_file();
let mut mapping = Mapping::new();
mapping.insert("mixed-port".into(), port.into());
Config::clash().data().patch_config(mapping);
let _ = Config::clash().data().save_config();
// 启动核心
log::trace!("init config");
log_err!(Config::init_config());
log::trace!("launch core");
log_err!(CoreManager::global().init());
// setup a simple http server for singleton
log::trace!("launch embed server");
server::embed_server(app.app_handle());
log::trace!("init system tray");
log_err!(tray::Tray::update_systray(&app.app_handle()));
let silent_start = { Config::verge().data().enable_silent_start.clone() };
let silent_start = { Config::verge().data().enable_silent_start };
if !silent_start.unwrap_or(false) {
create_window(&app.app_handle());
}
@@ -32,6 +96,13 @@ pub fn resolve_setup(app: &mut App) {
log_err!(handle::Handle::update_systray_part());
log_err!(hotkey::Hotkey::global().init(app.app_handle()));
log_err!(timer::Timer::global().init());
let argvs: Vec<String> = std::env::args().collect();
if argvs.len() > 1 {
tauri::async_runtime::block_on(async {
resolve_scheme(argvs[1].to_owned()).await;
});
}
}
/// reset system proxy
@@ -43,63 +114,153 @@ pub fn resolve_reset() {
/// create main window
pub fn create_window(app_handle: &AppHandle) {
if let Some(window) = app_handle.get_window("main") {
let _ = window.unminimize();
let _ = window.show();
let _ = window.set_focus();
trace_err!(window.unminimize(), "set win unminimize");
trace_err!(window.show(), "set win visible");
trace_err!(window.set_focus(), "set win focus");
return;
}
let builder = tauri::window::WindowBuilder::new(
let mut builder = tauri::window::WindowBuilder::new(
app_handle,
"main".to_string(),
tauri::WindowUrl::App("index.html".into()),
)
.title("Clash Verge")
.center()
.visible(false)
.fullscreen(false)
.min_inner_size(600.0, 520.0);
#[cfg(target_os = "windows")]
{
use std::time::Duration;
use tokio::time::sleep;
use window_shadows::set_shadow;
match builder
.decorations(false)
.transparent(true)
.inner_size(800.0, 636.0)
.visible(false)
.build()
{
Ok(_) => {
let app_handle = app_handle.clone();
if let Some(window) = app_handle.get_window("main") {
let _ = set_shadow(&window, true);
}
tauri::async_runtime::spawn(async move {
sleep(Duration::from_secs(1)).await;
if let Some(window) = app_handle.get_window("main") {
let _ = window.show();
let _ = window.unminimize();
let _ = window.set_focus();
}
});
match Config::verge().latest().window_size_position.clone() {
Some(size_pos) if size_pos.len() == 4 => {
let size = (size_pos[0], size_pos[1]);
let pos = (size_pos[2], size_pos[3]);
let w = size.0.clamp(600.0, f64::INFINITY);
let h = size.1.clamp(520.0, f64::INFINITY);
builder = builder.inner_size(w, h).position(pos.0, pos.1);
}
_ => {
#[cfg(target_os = "windows")]
{
builder = builder.inner_size(800.0, 636.0).center();
}
Err(err) => log::error!(target: "app", "{err}"),
#[cfg(target_os = "macos")]
{
builder = builder.inner_size(800.0, 642.0).center();
}
#[cfg(target_os = "linux")]
{
builder = builder.inner_size(800.0, 642.0).center();
}
}
};
#[cfg(target_os = "windows")]
let window = builder
.decorations(false)
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
.transparent(true)
.visible(false)
.build();
#[cfg(target_os = "macos")]
let window = builder
.decorations(true)
.hidden_title(true)
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build();
#[cfg(target_os = "linux")]
let window = builder.decorations(false).transparent(true).build();
match window {
Ok(win) => {
let is_maximized = Config::verge()
.latest()
.window_is_maximized
.unwrap_or(false);
log::trace!("try to calculate the monitor size");
let center = (|| -> Result<bool> {
let mut center = false;
let monitor = win.current_monitor()?.ok_or(anyhow::anyhow!(""))?;
let size = monitor.size();
let pos = win.outer_position()?;
if pos.x < -400
|| pos.x > (size.width - 200) as i32
|| pos.y < -200
|| pos.y > (size.height - 200) as i32
{
center = true;
}
Ok(center)
})();
if center.unwrap_or(true) {
trace_err!(win.center(), "set win center");
}
trace_err!(set_shadow(&win, true), "set win shadow");
if is_maximized {
trace_err!(win.maximize(), "set win maximize");
}
}
Err(_) => {
log::error!("failed to create window");
return;
}
}
}
#[cfg(target_os = "macos")]
crate::log_err!(builder.decorations(true).inner_size(800.0, 642.0).build());
/// save window size and position
pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) -> Result<()> {
let verge = Config::verge();
let mut verge = verge.latest();
#[cfg(target_os = "linux")]
crate::log_err!(builder
.decorations(false)
.transparent(true)
.inner_size(800.0, 636.0)
.build());
if save_to_file {
verge.save_file()?;
}
let win = app_handle
.get_window("main")
.ok_or(anyhow::anyhow!("failed to get window"))?;
let scale = win.scale_factor()?;
let size = win.inner_size()?;
let size = size.to_logical::<f64>(scale);
let pos = win.outer_position()?;
let pos = pos.to_logical::<f64>(scale);
let is_maximized = win.is_maximized()?;
verge.window_is_maximized = Some(is_maximized);
if !is_maximized && size.width >= 600.0 && size.height >= 520.0 {
verge.window_size_position = Some(vec![size.width, size.height, pos.x, pos.y]);
}
Ok(())
}
pub async fn resolve_scheme(param: String) {
let url = param
.trim_start_matches("clash://install-config/?url=")
.trim_start_matches("clash://install-config?url=");
let option = PrfOption {
user_agent: None,
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)
.title("Clash Verge")
.body("Import profile success")
.show()
.unwrap();
};
} else {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body("Import profile failed")
.show()
.unwrap();
log::error!("failed to parse url: {}", url);
}
}
+39 -5
View File
@@ -4,19 +4,42 @@ use super::resolve;
use crate::config::IVerge;
use anyhow::{bail, Result};
use port_scanner::local_port_available;
use std::convert::Infallible;
use tauri::AppHandle;
use warp::Filter;
#[derive(serde::Deserialize, Debug)]
struct QueryParam {
param: String,
}
/// check whether there is already exists
pub fn check_singleton() -> Result<()> {
let port = IVerge::get_singleton_port();
if !local_port_available(port) {
tauri::async_runtime::block_on(async {
let url = format!("http://127.0.0.1:{port}/commands/visible");
let resp = reqwest::get(url).await?.text().await?;
let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping"))
.await?
.text()
.await?;
if &resp == "ok" {
let argvs: Vec<String> = std::env::args().collect();
if argvs.len() > 1 {
let param = argvs[1].as_str();
reqwest::get(format!(
"http://127.0.0.1:{port}/commands/scheme?param={param}"
))
.await?
.text()
.await?;
} else {
reqwest::get(format!("http://127.0.0.1:{port}/commands/visible"))
.await?
.text()
.await?;
}
bail!("app exists");
}
@@ -34,11 +57,22 @@ pub fn embed_server(app_handle: AppHandle) {
let port = IVerge::get_singleton_port();
tauri::async_runtime::spawn(async move {
let commands = warp::path!("commands" / "visible").map(move || {
let ping = warp::path!("commands" / "ping").map(move || "ok");
let visible = warp::path!("commands" / "visible").map(move || {
resolve::create_window(&app_handle);
format!("ok")
"ok"
});
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;
let scheme = warp::path!("commands" / "scheme")
.and(warp::query::<QueryParam>())
.and_then(scheme_handler);
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
resolve::resolve_scheme(query.param).await;
Ok("ok")
}
let commands = ping.or(visible).or(scheme);
warp::serve(commands).run(([127, 0, 0, 1], port)).await;
});
}
+23 -16
View File
@@ -1,36 +1,43 @@
///! Some config file template
//! Some config file template
/// template for new a profile item
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge
pub const ITEM_LOCAL: &str = "# Profile Template for Clash Verge
proxies:
proxies: []
proxy-groups:
proxy-groups: []
rules:
rules: []
";
/// enhanced profile
pub const ITEM_MERGE: &str = "# Merge Template for clash verge
# The `Merge` format used to enhance profile
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge
prepend-rules:
prepend-rules: []
prepend-proxies:
prepend-rule-providers: {}
prepend-proxy-groups:
prepend-proxies: []
append-rules:
prepend-proxy-providers: {}
append-proxies:
prepend-proxy-groups: []
append-proxy-groups:
append-rules: []
append-rule-providers: {}
append-proxies: []
append-proxy-providers: {}
append-proxy-groups: []
";
/// enhanced profile
pub const ITEM_SCRIPT: &str = "// Define the `main` function
pub const ITEM_SCRIPT: &str = "// Define main function (script entry)
function main(params) {
return params;
function main(config) {
return config;
}
";
+14
View File
@@ -0,0 +1,14 @@
#[cfg(target_os = "linux")]
pub fn linux_elevator() -> &'static str {
use std::process::Command;
match Command::new("which").arg("pkexec").output() {
Ok(output) => {
if output.stdout.is_empty() {
"sudo"
} else {
"pkexec"
}
}
Err(_) => "sudo",
}
}
-69
View File
@@ -1,69 +0,0 @@
#![cfg(target_os = "windows")]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
//!
//! From https://github.com/tauri-apps/window-vibrancy/blob/dev/src/windows.rs
//!
use windows_sys::Win32::{
Foundation::*,
System::{LibraryLoader::*, SystemInformation::*},
};
fn get_function_impl(library: &str, function: &str) -> Option<FARPROC> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
let module = unsafe { LoadLibraryA(library.as_ptr()) };
if module == 0 {
return None;
}
Some(unsafe { GetProcAddress(module, function.as_ptr()) })
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe {
std::mem::transmute::<::windows_sys::Win32::Foundation::FARPROC, $func>(f)
})
};
}
/// Returns a tuple of (major, minor, buildnumber)
fn get_windows_ver() -> Option<(u32, u32, u32)> {
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32;
let handle = get_function!("ntdll.dll", RtlGetVersion);
if let Some(rtl_get_version) = handle {
unsafe {
let mut vi = OSVERSIONINFOW {
dwOSVersionInfoSize: 0,
dwMajorVersion: 0,
dwMinorVersion: 0,
dwBuildNumber: 0,
dwPlatformId: 0,
szCSDVersion: [0; 128],
};
let status = (rtl_get_version)(&mut vi as _);
if status >= 0 {
Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber))
} else {
None
}
}
} else {
None
}
}
pub fn is_win11() -> bool {
let v = get_windows_ver().unwrap_or_default();
v.2 >= 22000
}
#[test]
fn test_version() {
dbg!(get_windows_ver().unwrap_or_default());
}
+34 -34
View File
@@ -1,23 +1,19 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": {
"productName": "Clash Verge",
"version": "1.2.0"
"version": "1.6.1"
},
"build": {
"distDir": "../dist",
"devPath": "http://localhost:3000/",
"beforeDevCommand": "yarn run web:dev",
"beforeBuildCommand": "yarn run web:build"
"beforeDevCommand": "pnpm run web:dev",
"beforeBuildCommand": "pnpm run web:build"
},
"tauri": {
"systemTray": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "top.gydi.clashverge",
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@@ -26,38 +22,20 @@
"icons/icon.ico"
],
"resources": ["resources"],
"externalBin": ["sidecar/clash", "sidecar/clash-meta"],
"externalBin": ["sidecar/clash-meta", "sidecar/clash-meta-alpha"],
"copyright": "© 2022 zzzgydi All Rights Reserved",
"category": "DeveloperTool",
"shortDescription": "A Clash GUI based on tauri.",
"longDescription": "A Clash GUI based on tauri.",
"deb": {
"depends": []
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "",
"exceptionDomain": "",
"signingIdentity": null,
"entitlements": null
},
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"wix": {
"language": ["zh-CN", "en-US"]
}
}
"shortDescription": "A Clash Meta GUI based on tauri.",
"longDescription": "A Clash Meta GUI based on tauri."
},
"updater": {
"active": true,
"endpoints": [
"https://github.com/zzzgydi/clash-verge/releases/download/updater/update.json",
"https://hub.fastgit.xyz/zzzgydi/clash-verge/releases/download/updater/update-proxy.json"
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json"
],
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDExNUFBNTBBN0FDNEFBRTUKUldUbHFzUjZDcVZhRVRJM25NS3NkSFlFVElxUkNZMzZ6bHUwRVJjb2F3alJXVzRaeDdSaTA2YWYK"
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
},
"allowlist": {
"shell": {
@@ -71,11 +49,33 @@
},
"globalShortcut": {
"all": true
},
"clipboard": {
"all": true
},
"notification": {
"all": true
},
"dialog": {
"all": false,
"open": true
},
"protocol": {
"asset": true,
"assetScope": ["$APPDATA/**", "$RESOURCE/../**"]
},
"path": {
"all": true
},
"fs": {
"exists": true,
"readFile": true,
"scope": ["$APPDATA/**", "$RESOURCE/../**", "**"]
}
},
"windows": [],
"security": {
"csp": "script-src 'unsafe-eval' 'self'; default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; img-src data: 'self';"
"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';"
}
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"systemTray": {
"iconPath": "icons/tray-icon.png"
},
"bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["deb", "appimage", "updater"],
"deb": {
"depends": ["openssl"],
"desktopTemplate": "./template/clash-verge.desktop"
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"systemTray": {
"iconPath": "icons/mac-tray-icon.png",
"iconAsTemplate": true
},
"bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["app", "dmg", "updater"],
"macOS": {
"frameworks": [],
"minimumSystemVersion": "10.15",
"exceptionDomain": "",
"signingIdentity": null,
"entitlements": null
}
}
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"systemTray": {
"iconPath": "icons/tray-icon.png"
},
"bundle": {
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
"targets": ["nsis", "updater"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "",
"webviewInstallMode": {
"type": "embedBootstrapper",
"silent": true
},
"nsis": {
"displayLanguageSelector": true,
"installerIcon": "icons/icon.ico",
"languages": ["SimpChinese", "English"],
"license": "../LICENSE",
"installMode": "perMachine",
"template": "./template/installer.nsi"
}
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Categories={{{categories}}}
Comment={{{comment}}}
Exec={{{exec}}} %u
Icon={{{icon}}}
Name={{{name}}}
Terminal=false
Type=Application
MimeType=x-scheme-handler/clash;
+852
View File
@@ -0,0 +1,852 @@
; This file is copied from https://github.com/tauri-apps/tauri/blob/tauri-v1.5/tooling/bundler/src/bundle/windows/templates/installer.nsi
; and edit to fit the needs of the project. the latest tauri 2.x has a different base nsi script.
Unicode true
; Set the compression algorithm. Default is LZMA.
!if "{{compression}}" == ""
SetCompressor /SOLID lzma
!else
SetCompressor /SOLID "{{compression}}"
!endif
!include MUI2.nsh
!include FileFunc.nsh
!include x64.nsh
!include WordFunc.nsh
!include "LogicLib.nsh"
!include "StrFunc.nsh"
!addplugindir "$%AppData%\Local\NSIS\"
${StrCase}
${StrLoc}
!define MANUFACTURER "{{manufacturer}}"
!define PRODUCTNAME "{{product_name}}"
!define VERSION "{{version}}"
!define VERSIONWITHBUILD "{{version_with_build}}"
!define SHORTDESCRIPTION "{{short_description}}"
!define INSTALLMODE "{{install_mode}}"
!define LICENSE "{{license}}"
!define INSTALLERICON "{{installer_icon}}"
!define SIDEBARIMAGE "{{sidebar_image}}"
!define HEADERIMAGE "{{header_image}}"
!define MAINBINARYNAME "{{main_binary_name}}"
!define MAINBINARYSRCPATH "{{main_binary_path}}"
!define BUNDLEID "{{bundle_id}}"
!define COPYRIGHT "{{copyright}}"
!define OUTFILE "{{out_file}}"
!define ARCH "{{arch}}"
!define PLUGINSPATH "{{additional_plugins_path}}"
!define ALLOWDOWNGRADES "{{allow_downgrades}}"
!define DISPLAYLANGUAGESELECTOR "{{display_language_selector}}"
!define INSTALLWEBVIEW2MODE "{{install_webview2_mode}}"
!define WEBVIEW2INSTALLERARGS "{{webview2_installer_args}}"
!define WEBVIEW2BOOTSTRAPPERPATH "{{webview2_bootstrapper_path}}"
!define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
!define ESTIMATEDSIZE "{{estimated_size}}"
Name "${PRODUCTNAME}"
BrandingText "${COPYRIGHT}"
OutFile "${OUTFILE}"
VIProductVersion "${VERSIONWITHBUILD}"
VIAddVersionKey "ProductName" "${PRODUCTNAME}"
VIAddVersionKey "FileDescription" "${SHORTDESCRIPTION}"
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
VIAddVersionKey "FileVersion" "${VERSION}"
VIAddVersionKey "ProductVersion" "${VERSION}"
; Plugins path, currently exists for linux only
!if "${PLUGINSPATH}" != ""
!addplugindir "${PLUGINSPATH}"
!endif
!if "${UNINSTALLERSIGNCOMMAND}" != ""
!uninstfinalize '${UNINSTALLERSIGNCOMMAND}'
!endif
; Handle install mode, `perUser`, `perMachine` or `both`
!if "${INSTALLMODE}" == "perMachine"
RequestExecutionLevel highest
!endif
!if "${INSTALLMODE}" == "currentUser"
RequestExecutionLevel user
!endif
!if "${INSTALLMODE}" == "both"
!define MULTIUSER_MUI
!define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}"
!define MULTIUSER_INSTALLMODE_COMMANDLINE
!if "${ARCH}" == "x64"
!define MULTIUSER_USE_PROGRAMFILES64
!else if "${ARCH}" == "arm64"
!define MULTIUSER_USE_PROGRAMFILES64
!endif
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}"
!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser"
!define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME
!define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation
!define MULTIUSER_EXECUTIONLEVEL Highest
!include MultiUser.nsh
!endif
; installer icon
!if "${INSTALLERICON}" != ""
!define MUI_ICON "${INSTALLERICON}"
!endif
; installer sidebar image
!if "${SIDEBARIMAGE}" != ""
!define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}"
!endif
; installer header image
!if "${HEADERIMAGE}" != ""
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}"
!endif
; Define registry key to store installer language
!define MUI_LANGDLL_REGISTRY_ROOT "HKCU"
!define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}"
!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
; Installer pages, must be ordered as they appear
; 1. Welcome Page
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MUI_PAGE_WELCOME
; 2. License Page (if defined)
!if "${LICENSE}" != ""
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MUI_PAGE_LICENSE "${LICENSE}"
!endif
; 3. Install mode (if it is set to `both`)
!if "${INSTALLMODE}" == "both"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MULTIUSER_PAGE_INSTALLMODE
!endif
; 4. Custom page to ask user if he wants to reinstall/uninstall
; only if a previous installtion was detected
Var ReinstallPageCheck
Page custom PageReinstall PageLeaveReinstall
Function PageReinstall
; Uninstall previous WiX installation if exists.
;
; A WiX installer stores the isntallation info in registry
; using a UUID and so we have to loop through all keys under
; `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`
; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER}
;
; This has a potentional issue that there maybe another installation that matches
; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer,
; however, this should be fine since the user will have to confirm the uninstallation
; and they can chose to abort it if doesn't make sense.
StrCpy $0 0
wix_loop:
EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on
IntOp $0 $0 + 1
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName"
ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher"
StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString"
${StrCase} $R1 $R0 "L"
${StrLoc} $R0 $R1 "msiexec" ">"
StrCmp $R0 0 0 wix_done
StrCpy $R7 "wix"
StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1"
Goto compare_version
wix_done:
; Check if there is an existing installation, if not, abort the reinstall page
ReadRegStr $R0 SHCTX "${UNINSTKEY}" ""
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
${IfThen} "$R0$R1" == "" ${|} Abort ${|}
; Compare this installar version with the existing installation
; and modify the messages presented to the user accordingly
compare_version:
StrCpy $R4 "$(older)"
${If} $R7 == "wix"
ReadRegStr $R0 HKLM "$R6" "DisplayVersion"
${Else}
ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion"
${EndIf}
${IfThen} $R0 == "" ${|} StrCpy $R4 "$(unknown)" ${|}
nsis_tauri_utils::SemverCompare "${VERSION}" $R0
Pop $R0
; Reinstalling the same version
${If} $R0 == 0
StrCpy $R1 "$(alreadyInstalledLong)"
StrCpy $R2 "$(addOrReinstall)"
StrCpy $R3 "$(uninstallApp)"
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)"
StrCpy $R5 "2"
; Upgrading
${ElseIf} $R0 == 1
StrCpy $R1 "$(olderOrUnknownVersionInstalled)"
StrCpy $R2 "$(uninstallBeforeInstalling)"
StrCpy $R3 "$(dontUninstall)"
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
StrCpy $R5 "1"
; Downgrading
${ElseIf} $R0 == -1
StrCpy $R1 "$(newerVersionInstalled)"
StrCpy $R2 "$(uninstallBeforeInstalling)"
!if "${ALLOWDOWNGRADES}" == "true"
StrCpy $R3 "$(dontUninstall)"
!else
StrCpy $R3 "$(dontUninstallDowngrade)"
!endif
!insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
StrCpy $R5 "1"
${Else}
Abort
${EndIf}
Call SkipIfPassive
nsDialogs::Create 1018
Pop $R4
${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}
${NSD_CreateLabel} 0 0 100% 24u $R1
Pop $R1
${NSD_CreateRadioButton} 30u 50u -30u 8u $R2
Pop $R2
${NSD_OnClick} $R2 PageReinstallUpdateSelection
${NSD_CreateRadioButton} 30u 70u -30u 8u $R3
Pop $R3
; disable this radio button if downgrading and downgrades are disabled
!if "${ALLOWDOWNGRADES}" == "false"
${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|}
!endif
${NSD_OnClick} $R3 PageReinstallUpdateSelection
; Check the first radio button if this the first time
; we enter this page or if the second button wasn't
; selected the last time we were on this page
${If} $ReinstallPageCheck != 2
SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0
${Else}
SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0
${EndIf}
${NSD_SetFocus} $R2
nsDialogs::Show
FunctionEnd
Function PageReinstallUpdateSelection
${NSD_GetState} $R2 $R1
${If} $R1 == ${BST_CHECKED}
StrCpy $ReinstallPageCheck 1
${Else}
StrCpy $ReinstallPageCheck 2
${EndIf}
FunctionEnd
Function PageLeaveReinstall
${NSD_GetState} $R2 $R1
; $R5 holds whether we are reinstalling the same version or not
; $R5 == "1" -> different versions
; $R5 == "2" -> same version
;
; $R1 holds the radio buttons state. its meaning is dependant on the context
StrCmp $R5 "1" 0 +2 ; Existing install is not the same version?
StrCmp $R1 "1" reinst_uninstall reinst_done ; $R1 == "1", then user chose to uninstall existing version, otherwise skip uninstalling
StrCmp $R1 "1" reinst_done ; Same version? skip uninstalling
reinst_uninstall:
HideWindow
ClearErrors
${If} $R7 == "wix"
ReadRegStr $R1 HKLM "$R6" "UninstallString"
ExecWait '$R1' $0
${Else}
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
ExecWait '$R1 /P _?=$4' $0
${EndIf}
BringToFront
${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code
${If} $0 <> 0
${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe"
${If} $0 = 1 ; User aborted uninstaller?
StrCmp $R5 "2" 0 +2 ; Is the existing install the same version?
Quit ; ...yes, already installed, we are done
Abort
${EndIf}
MessageBox MB_ICONEXCLAMATION "$(unableToUninstall)"
Abort
${Else}
StrCpy $0 $R1 1
${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString
Delete $R1
RMDir $INSTDIR
${EndIf}
reinst_done:
FunctionEnd
; 5. Choose install directoy page
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MUI_PAGE_DIRECTORY
; 6. Start menu shortcut page
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
Var AppStartMenuFolder
!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder
; 7. Installation page
!insertmacro MUI_PAGE_INSTFILES
; 8. Finish page
;
; Don't auto jump to finish page after installation page,
; because the installation page has useful info that can be used debug any issues with the installer.
!define MUI_FINISHPAGE_NOAUTOCLOSE
; Use show readme button in the finish page as a button create a desktop shortcut
!define MUI_FINISHPAGE_SHOWREADME
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
; Show run app after installation.
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
!insertmacro MUI_PAGE_FINISH
; Uninstaller Pages
; 1. Confirm uninstall page
Var DeleteAppDataCheckbox
Var DeleteAppDataCheckboxState
!define /ifndef WS_EX_LAYOUTRTL 0x00400000
!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow
Function un.ConfirmShow
FindWindow $1 "#32770" "" $HWNDPARENT ; Find inner dialog
${If} $(^RTL) == 1
System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
${Else}
System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
${EndIf}
Pop $DeleteAppDataCheckbox
SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1
SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1
FunctionEnd
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave
Function un.ConfirmLeave
SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState
FunctionEnd
!insertmacro MUI_UNPAGE_CONFIRM
; 2. Uninstalling Page
!insertmacro MUI_UNPAGE_INSTFILES
;Languages
{{#each languages}}
!insertmacro MUI_LANGUAGE "{{this}}"
{{/each}}
!insertmacro MUI_RESERVEFILE_LANGDLL
{{#each language_files}}
!include "{{this}}"
{{/each}}
!macro SetContext
!if "${INSTALLMODE}" == "currentUser"
SetShellVarContext current
!else if "${INSTALLMODE}" == "perMachine"
SetShellVarContext all
!endif
${If} ${RunningX64}
!if "${ARCH}" == "x64"
SetRegView 64
!else if "${ARCH}" == "arm64"
SetRegView 64
!else
SetRegView 32
!endif
${EndIf}
!macroend
Var PassiveMode
Function .onInit
${GetOptions} $CMDLINE "/P" $PassiveMode
IfErrors +2 0
StrCpy $PassiveMode 1
!if "${DISPLAYLANGUAGESELECTOR}" == "true"
!insertmacro MUI_LANGDLL_DISPLAY
!endif
!insertmacro SetContext
${If} $INSTDIR == ""
; Set default install location
!if "${INSTALLMODE}" == "perMachine"
${If} ${RunningX64}
!if "${ARCH}" == "x64"
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
!else if "${ARCH}" == "arm64"
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
!else
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
!endif
${Else}
StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
${EndIf}
!else if "${INSTALLMODE}" == "currentUser"
StrCpy $INSTDIR "$LOCALAPPDATA\${PRODUCTNAME}"
!endif
Call RestorePreviousInstallLocation
${EndIf}
!if "${INSTALLMODE}" == "both"
!insertmacro MULTIUSER_INIT
!endif
FunctionEnd
!macro CheckAllVergeProcesses
; Check if Clash Verge.exe is running
nsis_tauri_utils::FindProcess "Clash Verge.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill Clash Verge.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "Clash Verge.exe"
!else
nsis_tauri_utils::KillProcess "Clash Verge.exe"
!endif
${EndIf}
; Check if clash-verge-service.exe is running
nsis_tauri_utils::FindProcess "clash-verge-service.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill clash-verge-service.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
!else
nsis_tauri_utils::KillProcess "clash-verge-service.exe"
!endif
${EndIf}
; Check if clash-meta-alpha.exe is running
nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill clash-meta-alpha.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
!else
nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
!endif
${EndIf}
; Check if clash-meta.exe is running
nsis_tauri_utils::FindProcess "clash-meta.exe"
${If} $R0 != 0
; Kill the process
DetailPrint "Kill clash-meta.exe..."
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
!else
nsis_tauri_utils::KillProcess "clash-meta.exe"
!endif
${EndIf}
!macroend
!macro StartVergeService
; Check if the service exists
SimpleSC::ExistsService "clash_verge_service"
Pop $0 ; 0:service exists;other: service not exists
; Service exists
${If} $0 == 0
Push $0
; Check if the service is running
SimpleSC::ServiceIsRunning "clash_verge_service"
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
${If} $0 == 0
Push $0
${If} $1 == 0
DetailPrint "Restart Clash Verge Service..."
SimpleSC::StartService "clash_verge_service" "" 30
${EndIf}
${ElseIf} $0 != 0
Push $0
SimpleSC::GetErrorMessage
Pop $0
MessageBox MB_OK|MB_ICONSTOP "Check Service Status Error ($0)"
${EndIf}
${EndIf}
!macroend
!macro RemoveVergeService
; Check if the service exists
SimpleSC::ExistsService "clash_verge_service"
Pop $0 ; 0:service exists;other: service not exists
; Service exists
${If} $0 == 0
Push $0
; Check if the service is running
SimpleSC::ServiceIsRunning "clash_verge_service"
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
Pop $1 ; returns 1 (service is running) - returns 0 (service is not running)
${If} $0 == 0
Push $0
${If} $1 == 1
DetailPrint "Stop Clash Verge Service..."
SimpleSC::StopService "clash_verge_service" 1 30
Pop $0 ; returns an errorcode (<>0) otherwise success (0)
${If} $0 == 0
DetailPrint "Removing Clash Verge Service..."
SimpleSC::RemoveService "clash_verge_service"
${ElseIf} $0 != 0
Push $0
SimpleSC::GetErrorMessage
Pop $0
MessageBox MB_OK|MB_ICONSTOP "Clash Verge Service Stop Error ($0)"
${EndIf}
${ElseIf} $1 == 0
DetailPrint "Removing Clash Verge Service..."
SimpleSC::RemoveService "clash_verge_service"
${EndIf}
${ElseIf} $0 != 0
Push $0
SimpleSC::GetErrorMessage
Pop $0
MessageBox MB_OK|MB_ICONSTOP "Check Service Status Error ($0)"
${EndIf}
${EndIf}
!macroend
Section EarlyChecks
; Abort silent installer if downgrades is disabled
!if "${ALLOWDOWNGRADES}" == "false"
IfSilent 0 silent_downgrades_done
; If downgrading
${If} $R0 == -1
System::Call 'kernel32::AttachConsole(i -1)i.r0'
${If} $0 != 0
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
FileWrite $0 "$(silentDowngrades)"
${EndIf}
Abort
${EndIf}
silent_downgrades_done:
!endif
SectionEnd
Section WebView2
; Check if Webview2 is already installed and skip this section
${If} ${RunningX64}
ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${Else}
ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${EndIf}
ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
StrCmp $4 "" 0 webview2_done
StrCmp $5 "" 0 webview2_done
; Webview2 install modes
!if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
DetailPrint "$(webview2Downloading)"
nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
Pop $0
${If} $0 == 0
DetailPrint "$(webview2DownloadSuccess)"
${Else}
DetailPrint "$(webview2DownloadError)"
Abort "$(webview2AbortError)"
${EndIf}
StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
Goto install_webview2
!endif
!if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper"
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}"
DetailPrint "$(installingWebview2)"
StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
Goto install_webview2
!endif
!if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
DetailPrint "$(installingWebview2)"
StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
Goto install_webview2
!endif
Goto webview2_done
install_webview2:
DetailPrint "$(installingWebview2)"
; $6 holds the path to the webview2 installer
ExecWait "$6 ${WEBVIEW2INSTALLERARGS} /install" $1
${If} $1 == 0
DetailPrint "$(webview2InstallSuccess)"
${Else}
DetailPrint "$(webview2InstallError)"
Abort "$(webview2AbortError)"
${EndIf}
webview2_done:
SectionEnd
!macro CheckIfAppIsRunning
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::FindProcessCurrentUser "${MAINBINARYNAME}.exe"
!else
nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe"
!endif
Pop $R0
${If} $R0 = 0
IfSilent kill 0
${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|}
kill:
!if "${INSTALLMODE}" == "currentUser"
nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe"
!else
nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
!endif
Pop $R0
Sleep 500
${If} $R0 = 0
Goto app_check_done
${Else}
IfSilent silent ui
silent:
System::Call 'kernel32::AttachConsole(i -1)i.r0'
${If} $0 != 0
System::Call 'kernel32::GetStdHandle(i -11)i.r0'
System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
FileWrite $0 "$(appRunning)$\n"
${EndIf}
Abort
ui:
Abort "$(failedToKillApp)"
${EndIf}
cancel:
Abort "$(appRunning)"
${EndIf}
app_check_done:
!macroend
Section Install
SetOutPath $INSTDIR
!insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses
; Copy main executable
File "${MAINBINARYSRCPATH}"
; Copy resources
{{#each resources_dirs}}
CreateDirectory "$INSTDIR\\{{this}}"
{{/each}}
{{#each resources}}
File /a "/oname={{this.[1]}}" "{{@key}}"
{{/each}}
; Copy external binaries
{{#each binaries}}
File /a "/oname={{this}}" "{{@key}}"
{{/each}}
!insertmacro StartVergeService
; Create uninstaller
WriteUninstaller "$INSTDIR\uninstall.exe"
; Save $INSTDIR in registry for future installations
WriteRegStr SHCTX "${MANUPRODUCTKEY}" "" $INSTDIR
!if "${INSTALLMODE}" == "both"
; Save install mode to be selected by default for the next installation such as updating
; or when uninstalling
WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1
!endif
; Registry information for add/remove programs
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}"
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\""
WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}"
WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}"
WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\""
WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1"
WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1"
WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "${ESTIMATEDSIZE}"
; Create start menu shortcut (GUI)
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
Call CreateStartMenuShortcut
!insertmacro MUI_STARTMENU_WRITE_END
; Create shortcuts for silent and passive installers, which
; can be disabled by passing `/NS` flag
; GUI installer has buttons for users to control creating them
IfSilent check_ns_flag 0
${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|}
Goto shortcuts_done
check_ns_flag:
${GetOptions} $CMDLINE "/NS" $R0
IfErrors 0 shortcuts_done
Call CreateDesktopShortcut
Call CreateStartMenuShortcut
shortcuts_done:
; Auto close this page for passive mode
${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|}
SectionEnd
Function .onInstSuccess
; Check for `/R` flag only in silent and passive installers because
; GUI installer has a toggle for the user to (re)start the app
IfSilent check_r_flag 0
${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|}
Goto run_done
check_r_flag:
${GetOptions} $CMDLINE "/R" $R0
IfErrors run_done 0
Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
run_done:
FunctionEnd
Function un.onInit
!insertmacro SetContext
!if "${INSTALLMODE}" == "both"
!insertmacro MULTIUSER_UNINIT
!endif
!insertmacro MUI_UNGETLANGUAGE
FunctionEnd
Function un.isDirectoryEmpty
Exch $0
Push $1
Push $2
StrCpy $2 0
FindFirst $1 $2 "$0\*.*"
loop:
StrCmp $2 "" done
StrCmp $2 "." next
StrCmp $2 ".." next
StrCpy $0 0
goto done
next:
FindNext $1 $2
goto loop
done:
FindClose $1
StrCmp $2 "" 0 +2
StrCpy $0 1
Pop $2
Pop $1
Exch $0
FunctionEnd
Section Uninstall
!insertmacro CheckIfAppIsRunning
!insertmacro CheckAllVergeProcesses
!insertmacro RemoveVergeService
; Delete the app directory and its content from disk
; Copy main executable
Delete "$INSTDIR\${MAINBINARYNAME}.exe"
; Delete resources
{{#each resources}}
Delete "$INSTDIR\\{{this.[1]}}"
{{/each}}
Delete "$INSTDIR\resources"
; Delete external binaries
{{#each binaries}}
Delete "$INSTDIR\\{{this}}"
{{/each}}
; Delete uninstaller
Delete "$INSTDIR\uninstall.exe"
; Remove InstallDir
Push "$INSTDIR"
Call un.isDirectoryEmpty
Pop $0
${If} $0 == 1
RMDir /R /REBOOTOK "$INSTDIR"
${Else}
MessageBox MB_OK "Install Directory is not Empty, Please remove it manually."
${EndIf}
; Remove start menu shortcut
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
RMDir "$SMPROGRAMS\$AppStartMenuFolder"
; Remove desktop shortcuts
Delete "$DESKTOP\${MAINBINARYNAME}.lnk"
; Remove registry information for add/remove programs
!if "${INSTALLMODE}" == "both"
DeleteRegKey SHCTX "${UNINSTKEY}"
!else if "${INSTALLMODE}" == "perMachine"
DeleteRegKey HKLM "${UNINSTKEY}"
!else
DeleteRegKey HKCU "${UNINSTKEY}"
!endif
DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
; Delete app data
${If} $DeleteAppDataCheckboxState == 1
SetShellVarContext current
RmDir /r "$APPDATA\${BUNDLEID}"
RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
${EndIf}
${GetOptions} $CMDLINE "/P" $R0
IfErrors +2 0
SetAutoClose true
SectionEnd
Function RestorePreviousInstallLocation
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
StrCmp $4 "" +2 0
StrCpy $INSTDIR $4
FunctionEnd
Function SkipIfPassive
${IfThen} $PassiveMode == 1 ${|} Abort ${|}
FunctionEnd
Function CreateDesktopShortcut
CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
FunctionEnd
Function CreateStartMenuShortcut
CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
FunctionEnd
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_118)"/>
<path d="M17.9917 9.66675C13.3917 9.66675 9.66669 13.4001 9.66669 18.0001C9.66669 22.6001 13.3917 26.3334 17.9917 26.3334C22.6 26.3334 26.3334 22.6001 26.3334 18.0001C26.3334 13.4001 22.6 9.66675 17.9917 9.66675ZM23.7667 14.6667H21.3084C21.0417 13.6251 20.6584 12.6251 20.1584 11.7001C21.6917 12.2251 22.9667 13.2917 23.7667 14.6667ZM18 11.3667C18.6917 12.3667 19.2334 13.4751 19.5917 14.6667H16.4084C16.7667 13.4751 17.3084 12.3667 18 11.3667ZM11.55 19.6667C11.4167 19.1334 11.3334 18.5751 11.3334 18.0001C11.3334 17.4251 11.4167 16.8667 11.55 16.3334H14.3667C14.3 16.8834 14.25 17.4334 14.25 18.0001C14.25 18.5667 14.3 19.1167 14.3667 19.6667H11.55ZM12.2334 21.3334H14.6917C14.9584 22.3751 15.3417 23.3751 15.8417 24.3001C14.3084 23.7751 13.0334 22.7167 12.2334 21.3334ZM14.6917 14.6667H12.2334C13.0334 13.2834 14.3084 12.2251 15.8417 11.7001C15.3417 12.6251 14.9584 13.6251 14.6917 14.6667ZM18 24.6334C17.3084 23.6334 16.7667 22.5251 16.4084 21.3334H19.5917C19.2334 22.5251 18.6917 23.6334 18 24.6334ZM19.95 19.6667H16.05C15.975 19.1167 15.9167 18.5667 15.9167 18.0001C15.9167 17.4334 15.975 16.8751 16.05 16.3334H19.95C20.025 16.8751 20.0834 17.4334 20.0834 18.0001C20.0834 18.5667 20.025 19.1167 19.95 19.6667ZM20.1584 24.3001C20.6584 23.3751 21.0417 22.3751 21.3084 21.3334H23.7667C22.9667 22.7084 21.6917 23.7751 20.1584 24.3001ZM21.6334 19.6667C21.7 19.1167 21.75 18.5667 21.75 18.0001C21.75 17.4334 21.7 16.8834 21.6334 16.3334H24.45C24.5834 16.8667 24.6667 17.4251 24.6667 18.0001C24.6667 18.5751 24.5834 19.1334 24.45 19.6667H21.6334Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_118" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#009038"/>
<stop offset="1" stop-color="#1CA350"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_127)"/>
<path d="M18.8334 22.1667H12.1667C11.7084 22.1667 11.3334 22.5417 11.3334 23.0001C11.3334 23.4584 11.7084 23.8334 12.1667 23.8334H18.8334C19.2917 23.8334 19.6667 23.4584 19.6667 23.0001C19.6667 22.5417 19.2917 22.1667 18.8334 22.1667ZM23.8334 15.5001H12.1667C11.7084 15.5001 11.3334 15.8751 11.3334 16.3334C11.3334 16.7917 11.7084 17.1667 12.1667 17.1667H23.8334C24.2917 17.1667 24.6667 16.7917 24.6667 16.3334C24.6667 15.8751 24.2917 15.5001 23.8334 15.5001ZM12.1667 20.5001H23.8334C24.2917 20.5001 24.6667 20.1251 24.6667 19.6667C24.6667 19.2084 24.2917 18.8334 23.8334 18.8334H12.1667C11.7084 18.8334 11.3334 19.2084 11.3334 19.6667C11.3334 20.1251 11.7084 20.5001 12.1667 20.5001ZM11.3334 13.0001C11.3334 13.4584 11.7084 13.8334 12.1667 13.8334H23.8334C24.2917 13.8334 24.6667 13.4584 24.6667 13.0001C24.6667 12.5417 24.2917 12.1667 23.8334 12.1667H12.1667C11.7084 12.1667 11.3334 12.5417 11.3334 13.0001Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_127" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#E96038"/>
<stop offset="1" stop-color="#E1451D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_113)"/>
<path d="M23.8333 18.8333H12.1667C11.25 18.8333 10.5 19.5833 10.5 20.5V23.8333C10.5 24.75 11.25 25.5 12.1667 25.5H23.8333C24.75 25.5 25.5 24.75 25.5 23.8333V20.5C25.5 19.5833 24.75 18.8333 23.8333 18.8333ZM13.8333 23.8333C12.9167 23.8333 12.1667 23.0833 12.1667 22.1667C12.1667 21.25 12.9167 20.5 13.8333 20.5C14.75 20.5 15.5 21.25 15.5 22.1667C15.5 23.0833 14.75 23.8333 13.8333 23.8333ZM23.8333 10.5H12.1667C11.25 10.5 10.5 11.25 10.5 12.1667V15.5C10.5 16.4167 11.25 17.1667 12.1667 17.1667H23.8333C24.75 17.1667 25.5 16.4167 25.5 15.5V12.1667C25.5 11.25 24.75 10.5 23.8333 10.5ZM13.8333 15.5C12.9167 15.5 12.1667 14.75 12.1667 13.8333C12.1667 12.9167 12.9167 12.1667 13.8333 12.1667C14.75 12.1667 15.5 12.9167 15.5 13.8333C15.5 14.75 14.75 15.5 13.8333 15.5Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_113" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#6038CB"/>
<stop offset="1" stop-color="#704ADC"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_108)"/>
<path d="M9.7167 16.3834C10.1417 16.8084 10.8167 16.85 11.275 16.4667C15.1667 13.2667 20.8167 13.2667 24.7167 16.4583C25.1834 16.8417 25.8667 16.8084 26.2917 16.3834C26.7834 15.8917 26.75 15.075 26.2084 14.6333C21.45 10.7417 14.5667 10.7417 9.80003 14.6333C9.25836 15.0667 9.2167 15.8834 9.7167 16.3834ZM16.1834 22.85L17.4084 24.075C17.7334 24.4 18.2584 24.4 18.5834 24.075L19.8084 22.85C20.2 22.4583 20.1167 21.7833 19.6167 21.525C18.6 21 17.3834 21 16.3584 21.525C15.8834 21.7833 15.7917 22.4583 16.1834 22.85ZM13.075 19.7417C13.4834 20.15 14.125 20.1917 14.6 19.85C16.6334 18.4084 19.3667 18.4084 21.4 19.85C21.875 20.1834 22.5167 20.15 22.925 19.7417L22.9334 19.7333C23.4334 19.2333 23.4 18.3834 22.825 17.975C19.9584 15.9 16.05 15.9 13.175 17.975C12.6 18.3917 12.5667 19.2333 13.075 19.7417Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_108" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#21B2CB"/>
<stop offset="1" stop-color="#3EC5D2"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_122)"/>
<path d="M15.5 24.6666C15.5 25.125 15.875 25.5 16.3333 25.5C16.7917 25.5 17.1667 25.125 17.1667 24.6666V22.1666C17.775 20.0166 19.725 19.275 21.475 19.6666L20.7416 20.4C20.4166 20.725 20.4166 21.25 20.7416 21.575C21.0666 21.9 21.5917 21.9 21.9167 21.575L24.075 19.4166C24.4 19.0916 24.4 18.5666 24.075 18.2416L21.9167 16.0833C21.8396 16.006 21.748 15.9447 21.6472 15.9029C21.5464 15.8611 21.4383 15.8396 21.3291 15.8396C21.22 15.8396 21.1119 15.8611 21.0111 15.9029C20.9103 15.9447 20.8187 16.006 20.7416 16.0833C20.4166 16.4083 20.4166 16.9333 20.7416 17.2583L21.475 18C20.2166 17.725 18.3667 18.0666 17.1667 19.1333V13.6916L17.9 14.425C18.225 14.75 18.75 14.75 19.075 14.425C19.4 14.1 19.4 13.575 19.075 13.25L16.9167 11.0916C16.8396 11.0144 16.748 10.9531 16.6472 10.9113C16.5464 10.8694 16.4383 10.8479 16.3292 10.8479C16.22 10.8479 16.1119 10.8694 16.0111 10.9113C15.9103 10.9531 15.8187 11.0144 15.7417 11.0916L13.5917 13.2416C13.2667 13.5666 13.2667 14.0916 13.5917 14.4166C13.9167 14.7416 14.4417 14.7416 14.7667 14.4166L15.5 13.6916V24.6666Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_122" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#FB4293"/>
<stop offset="1" stop-color="#F957A1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_137)"/>
<path d="M24.25 18.0001C24.25 17.8084 24.2416 17.6251 24.225 17.4334L25.775 16.2584C26.1083 16.0084 26.2 15.5417 25.9916 15.1751L24.4333 12.4834C24.225 12.1167 23.775 11.9667 23.3916 12.1334L21.6 12.8917C21.2916 12.6751 20.9666 12.4834 20.625 12.3251L20.3833 10.4001C20.3333 9.98341 19.975 9.66675 19.5583 9.66675H16.45C16.025 9.66675 15.6666 9.98341 15.6166 10.4001L15.375 12.3251C15.0333 12.4834 14.7083 12.6751 14.4 12.8917L12.6083 12.1334C12.225 11.9667 11.775 12.1167 11.5666 12.4834L10.0083 15.1834C9.79997 15.5501 9.89163 16.0084 10.225 16.2667L11.775 17.4417C11.7583 17.6251 11.75 17.8084 11.75 18.0001C11.75 18.1917 11.7583 18.3751 11.775 18.5667L10.225 19.7417C9.89163 19.9917 9.79997 20.4584 10.0083 20.8251L11.5666 23.5167C11.775 23.8834 12.225 24.0334 12.6083 23.8667L14.4 23.1084C14.7083 23.3251 15.0333 23.5167 15.375 23.6751L15.6166 25.6001C15.6666 26.0167 16.025 26.3334 16.4416 26.3334H19.55C19.9666 26.3334 20.325 26.0167 20.375 25.6001L20.6166 23.6751C20.9583 23.5167 21.2833 23.3251 21.5916 23.1084L23.3833 23.8667C23.7666 24.0334 24.2166 23.8834 24.425 23.5167L25.9833 20.8251C26.1916 20.4584 26.1 20.0001 25.7666 19.7417L24.2166 18.5667C24.2416 18.3751 24.25 18.1917 24.25 18.0001ZM18.0333 20.9167C16.425 20.9167 15.1166 19.6084 15.1166 18.0001C15.1166 16.3917 16.425 15.0834 18.0333 15.0834C19.6416 15.0834 20.95 16.3917 20.95 18.0001C20.95 19.6084 19.6416 20.9167 18.0333 20.9167Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_137" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#56718E"/>
<stop offset="1" stop-color="#4B6683"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_132)"/>
<path d="M18 17.1668C17.0834 17.1668 16.3334 17.9168 16.3334 18.8335C16.3334 19.7502 17.0834 20.5002 18 20.5002C18.9167 20.5002 19.6667 19.7502 19.6667 18.8335C19.6667 17.9168 18.9167 17.1668 18 17.1668ZM23 18.8335C23 15.8668 20.4084 13.5002 17.375 13.8752C15.1917 14.1418 13.3917 15.8835 13.0584 18.0585C12.7834 19.8502 13.4667 21.4835 14.6667 22.5585C15.0667 22.9168 15.6917 22.8335 15.9667 22.3668L15.975 22.3585C16.175 22.0085 16.0917 21.5835 15.7917 21.3085C14.9334 20.5335 14.4667 19.3335 14.775 18.0252C15.05 16.8418 16.0084 15.8835 17.1917 15.6002C19.375 15.0752 21.3334 16.7252 21.3334 18.8335C21.3334 19.8168 20.9 20.6918 20.225 21.3002C19.925 21.5668 19.8334 22.0002 20.0334 22.3502L20.0417 22.3585C20.3 22.8002 20.9 22.9335 21.2917 22.5918C22.3334 21.6752 23 20.3335 23 18.8335ZM17.025 10.5585C13.175 10.9918 10.0667 14.1668 9.70838 18.0252C9.41671 21.1085 10.8084 23.8752 13.0584 25.5335C13.4584 25.8252 14.025 25.7002 14.275 25.2752C14.4834 24.9168 14.3917 24.4502 14.0584 24.2002C12.1584 22.7918 11.0167 20.4085 11.425 17.7835C11.875 14.8668 14.3084 12.5418 17.2417 12.2168C21.2584 11.7585 24.6667 14.9002 24.6667 18.8335C24.6667 21.0418 23.5917 22.9835 21.9417 24.2002C21.6084 24.4502 21.5167 24.9085 21.725 25.2752C21.975 25.7085 22.5417 25.8252 22.9417 25.5335C25 24.0168 26.3334 21.5835 26.3334 18.8335C26.3334 13.9085 22.0584 9.98349 17.025 10.5585Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_971_132" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA800"/>
<stop offset="1" stop-color="#FFAC4B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

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