mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-12 21:00:33 +08:00
Compare commits
2 Commits
8975be054e
...
d1184bab2a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1184bab2a | ||
|
|
9e5da1a851 |
67
Cargo.lock
generated
67
Cargo.lock
generated
@ -1140,6 +1140,9 @@ dependencies = [
|
||||
"log",
|
||||
"nanoid",
|
||||
"network-interface",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"open",
|
||||
"parking_lot",
|
||||
@ -4673,9 +4676,38 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-core-text",
|
||||
"objc2-core-video",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
@ -4703,6 +4735,41 @@ dependencies = [
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-image"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-video"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
- 新增 macOS 托盘速率显示
|
||||
- 快捷键操作通知操作结果
|
||||
|
||||
### 🚀 优化改进
|
||||
|
||||
@ -67,8 +67,8 @@
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-yaml": "^5.4.1",
|
||||
"nanoid": "^5.1.7",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react": "19.2.5",
|
||||
"react-dom": "19.2.5",
|
||||
"react-error-boundary": "6.1.1",
|
||||
"react-hook-form": "^7.72.0",
|
||||
"react-i18next": "17.0.2",
|
||||
|
||||
246
pnpm-lock.yaml
generated
246
pnpm-lock.yaml
generated
@ -10,43 +10,43 @@ importers:
|
||||
dependencies:
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@dnd-kit/sortable':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
|
||||
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(react@19.2.4)
|
||||
version: 3.2.2(react@19.2.5)
|
||||
'@emotion/react':
|
||||
specifier: ^11.14.0
|
||||
version: 11.14.0(@types/react@19.2.14)(react@19.2.4)
|
||||
version: 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/styled':
|
||||
specifier: ^11.14.1
|
||||
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@juggle/resize-observer':
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
'@monaco-editor/react':
|
||||
specifier: ^4.7.0
|
||||
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@mui/icons-material':
|
||||
specifier: ^7.3.9
|
||||
version: 7.3.9(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
version: 7.3.9(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@mui/lab':
|
||||
specifier: 7.0.0-beta.17
|
||||
version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@mui/material':
|
||||
specifier: ^7.3.9
|
||||
version: 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.96.1
|
||||
version: 5.96.2(react@19.2.4)
|
||||
version: 5.96.2(react@19.2.5)
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@tanstack/react-virtual':
|
||||
specifier: ^3.13.23
|
||||
version: 3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 3.13.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@tauri-apps/api':
|
||||
specifier: 2.10.1
|
||||
version: 2.10.1
|
||||
@ -73,7 +73,7 @@ importers:
|
||||
version: 2.10.1
|
||||
ahooks:
|
||||
specifier: ^3.9.6
|
||||
version: 3.9.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 3.9.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
cidr-block:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0
|
||||
@ -82,7 +82,7 @@ importers:
|
||||
version: 1.11.20
|
||||
foxact:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 0.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
foxts:
|
||||
specifier: ^5.3.0
|
||||
version: 5.4.0
|
||||
@ -108,26 +108,26 @@ importers:
|
||||
specifier: ^5.1.7
|
||||
version: 5.1.7
|
||||
react:
|
||||
specifier: 19.2.4
|
||||
version: 19.2.4
|
||||
specifier: 19.2.5
|
||||
version: 19.2.5
|
||||
react-dom:
|
||||
specifier: 19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
specifier: 19.2.5
|
||||
version: 19.2.5(react@19.2.5)
|
||||
react-error-boundary:
|
||||
specifier: 6.1.1
|
||||
version: 6.1.1(react@19.2.4)
|
||||
version: 6.1.1(react@19.2.5)
|
||||
react-hook-form:
|
||||
specifier: ^7.72.0
|
||||
version: 7.72.1(react@19.2.4)
|
||||
version: 7.72.1(react@19.2.5)
|
||||
react-i18next:
|
||||
specifier: 17.0.2
|
||||
version: 17.0.2(i18next@26.0.3(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2)
|
||||
version: 17.0.2(i18next@26.0.3(typescript@6.0.2))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)
|
||||
react-markdown:
|
||||
specifier: 10.1.0
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.5)
|
||||
react-router:
|
||||
specifier: ^7.13.1
|
||||
version: 7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
version: 7.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
rehype-raw:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
@ -3101,10 +3101,10 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
react-dom@19.2.4:
|
||||
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
|
||||
react-dom@19.2.5:
|
||||
resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==}
|
||||
peerDependencies:
|
||||
react: ^19.2.4
|
||||
react: ^19.2.5
|
||||
|
||||
react-error-boundary@6.1.1:
|
||||
resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==}
|
||||
@ -3164,8 +3164,8 @@ packages:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
|
||||
react@19.2.4:
|
||||
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
||||
react@19.2.5:
|
||||
resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
@ -4331,29 +4331,29 @@ snapshots:
|
||||
'@biomejs/cli-win32-x64@2.4.10':
|
||||
optional: true
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@19.2.4)':
|
||||
'@dnd-kit/accessibility@3.1.1(react@19.2.5)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@dnd-kit/accessibility': 3.1.1(react@19.2.4)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
'@dnd-kit/accessibility': 3.1.1(react@19.2.5)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.2.5)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
|
||||
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.2.4)
|
||||
react: 19.2.4
|
||||
'@dnd-kit/core': 6.3.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.2.5)
|
||||
react: 19.2.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/utilities@3.2.2(react@19.2.4)':
|
||||
'@dnd-kit/utilities@3.2.2(react@19.2.5)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@emnapi/core@1.9.2':
|
||||
@ -4404,17 +4404,17 @@ snapshots:
|
||||
|
||||
'@emotion/memoize@0.9.0': {}
|
||||
|
||||
'@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4)':
|
||||
'@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.4)
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.5)
|
||||
'@emotion/utils': 1.4.2
|
||||
'@emotion/weak-memoize': 0.4.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
transitivePeerDependencies:
|
||||
@ -4430,16 +4430,16 @@ snapshots:
|
||||
|
||||
'@emotion/sheet@1.4.0': {}
|
||||
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)':
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/is-prop-valid': 1.4.0
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.4)
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.5)
|
||||
'@emotion/utils': 1.4.2
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
transitivePeerDependencies:
|
||||
@ -4447,9 +4447,9 @@ snapshots:
|
||||
|
||||
'@emotion/unitless@0.10.0': {}
|
||||
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.4)':
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.5)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
|
||||
'@emotion/utils@1.4.2': {}
|
||||
|
||||
@ -4617,70 +4617,70 @@ snapshots:
|
||||
dependencies:
|
||||
state-local: 1.0.7
|
||||
|
||||
'@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@monaco-editor/loader': 1.7.0
|
||||
monaco-editor: 0.55.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
'@mui/core-downloads-tracker@7.3.9': {}
|
||||
|
||||
'@mui/icons-material@7.3.9(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)':
|
||||
'@mui/icons-material@7.3.9(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/material': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
'@mui/material': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/material': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mui/system': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/material': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@mui/system': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@mui/types': 7.4.12(@types/react@19.2.14)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.5)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mui/material@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/core-downloads-tracker': 7.3.9
|
||||
'@mui/system': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/system': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@mui/types': 7.4.12(@types/react@19.2.14)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.5)
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/react-transition-group': 4.4.12(@types/react@19.2.14)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.2.3
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
react-is: 19.2.4
|
||||
react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react-transition-group: 4.4.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/private-theming@7.3.9(@types/react@19.2.14)(react@19.2.4)':
|
||||
'@mui/private-theming@7.3.9(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.5)
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/styled-engine@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)':
|
||||
'@mui/styled-engine@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@emotion/cache': 11.14.0
|
||||
@ -4688,25 +4688,25 @@ snapshots:
|
||||
'@emotion/sheet': 1.4.0
|
||||
csstype: 3.2.3
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
|
||||
'@mui/system@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)':
|
||||
'@mui/system@7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/private-theming': 7.3.9(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/styled-engine': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@mui/private-theming': 7.3.9(@types/react@19.2.14)(react@19.2.5)
|
||||
'@mui/styled-engine': 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(react@19.2.5)
|
||||
'@mui/types': 7.4.12(@types/react@19.2.14)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.4)
|
||||
'@mui/utils': 7.3.9(@types/react@19.2.14)(react@19.2.5)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.2.3
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.5)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/types@7.4.12(@types/react@19.2.14)':
|
||||
@ -4715,14 +4715,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/utils@7.3.9(@types/react@19.2.14)(react@19.2.4)':
|
||||
'@mui/utils@7.3.9(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@mui/types': 7.4.12(@types/react@19.2.14)
|
||||
'@types/prop-types': 15.7.15
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
react-is: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
@ -4992,22 +4992,22 @@ snapshots:
|
||||
|
||||
'@tanstack/query-core@5.96.2': {}
|
||||
|
||||
'@tanstack/react-query@5.96.2(react@19.2.4)':
|
||||
'@tanstack/react-query@5.96.2(react@19.2.5)':
|
||||
dependencies:
|
||||
'@tanstack/query-core': 5.96.2
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
|
||||
'@tanstack/react-table@8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@tanstack/react-table@8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@tanstack/table-core': 8.21.3
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
'@tanstack/react-virtual@3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@tanstack/react-virtual@3.13.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.13.23
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
'@tanstack/table-core@8.21.3': {}
|
||||
|
||||
@ -5344,7 +5344,7 @@ snapshots:
|
||||
|
||||
agent-base@9.0.0: {}
|
||||
|
||||
ahooks@3.9.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
ahooks@3.9.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@types/js-cookie': 3.0.6
|
||||
@ -5352,8 +5352,8 @@ snapshots:
|
||||
intersection-observer: 0.12.2
|
||||
js-cookie: 3.0.5
|
||||
lodash: 4.18.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
react-fast-compare: 3.2.2
|
||||
resize-observer-polyfill: 1.5.1
|
||||
screenfull: 5.2.0
|
||||
@ -5977,14 +5977,14 @@ snapshots:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
foxact@0.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
foxact@0.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
event-target-bus: 1.0.0
|
||||
server-only: 0.0.1
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
foxts@5.4.0: {}
|
||||
|
||||
@ -6763,37 +6763,37 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
react-dom@19.2.4(react@19.2.4):
|
||||
react-dom@19.2.5(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-error-boundary@6.1.1(react@19.2.4):
|
||||
react-error-boundary@6.1.1(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
|
||||
react-fast-compare@3.2.2: {}
|
||||
|
||||
react-hook-form@7.72.1(react@19.2.4):
|
||||
react-hook-form@7.72.1(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
|
||||
react-i18next@17.0.2(i18next@26.0.3(typescript@6.0.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.2):
|
||||
react-i18next@17.0.2(i18next@26.0.3(typescript@6.0.2))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 26.0.3(typescript@6.0.2)
|
||||
react: 19.2.4
|
||||
use-sync-external-store: 1.6.0(react@19.2.4)
|
||||
react: 19.2.5
|
||||
use-sync-external-store: 1.6.0(react@19.2.5)
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
typescript: 6.0.2
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@19.2.4: {}
|
||||
|
||||
react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4):
|
||||
react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.5):
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/mdast': 4.0.4
|
||||
@ -6802,7 +6802,7 @@ snapshots:
|
||||
hast-util-to-jsx-runtime: 2.3.6
|
||||
html-url-attributes: 3.0.1
|
||||
mdast-util-to-hast: 13.2.1
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
remark-parse: 11.0.0
|
||||
remark-rehype: 11.1.2
|
||||
unified: 11.0.5
|
||||
@ -6811,24 +6811,24 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
react-router@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
react-router@7.14.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||
dependencies:
|
||||
cookie: 1.1.1
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
set-cookie-parser: 2.7.2
|
||||
optionalDependencies:
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
react-transition-group@4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
react-transition-group@4.4.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
react@19.2.4: {}
|
||||
react@19.2.5: {}
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
@ -7171,9 +7171,9 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
use-sync-external-store@1.6.0(react@19.2.4):
|
||||
use-sync-external-store@1.6.0(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react: 19.2.5
|
||||
|
||||
validator@13.15.35: {}
|
||||
|
||||
|
||||
@ -109,6 +109,28 @@ rust_iso3166 = "0.1.14"
|
||||
# Use the git repo until the next release after v2.0.0.
|
||||
dark-light = { git = "https://github.com/rust-dark-light/dark-light" }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = { version = "0.3", features = [
|
||||
"NSString",
|
||||
"NSDictionary",
|
||||
"NSAttributedString",
|
||||
] }
|
||||
objc2-app-kit = { version = "0.3", features = [
|
||||
"NSAttributedString",
|
||||
"NSStatusItem",
|
||||
"NSStatusBarButton",
|
||||
"NSButton",
|
||||
"NSControl",
|
||||
"NSResponder",
|
||||
"NSView",
|
||||
"NSFont",
|
||||
"NSFontDescriptor",
|
||||
"NSColor",
|
||||
"NSParagraphStyle",
|
||||
"NSText",
|
||||
] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
deelevate = { workspace = true }
|
||||
runas = "=1.2.0"
|
||||
|
||||
@ -233,7 +233,7 @@ pub struct IVerge {
|
||||
)]
|
||||
pub webdav_password: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
#[cfg(target_os = "macos")]
|
||||
pub enable_tray_speed: Option<bool>,
|
||||
|
||||
// pub enable_tray_icon: Option<bool>,
|
||||
@ -438,6 +438,7 @@ impl IVerge {
|
||||
webdav_url: None,
|
||||
webdav_username: None,
|
||||
webdav_password: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
enable_tray_speed: Some(false),
|
||||
// enable_tray_icon: Some(true),
|
||||
tray_proxy_groups_display_mode: Some("default".into()),
|
||||
@ -543,6 +544,7 @@ impl IVerge {
|
||||
patch!(webdav_url);
|
||||
patch!(webdav_username);
|
||||
patch!(webdav_password);
|
||||
#[cfg(target_os = "macos")]
|
||||
patch!(enable_tray_speed);
|
||||
// patch!(enable_tray_icon);
|
||||
patch!(tray_proxy_groups_display_mode);
|
||||
|
||||
@ -25,7 +25,10 @@ use tauri::{
|
||||
AppHandle, Wry,
|
||||
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
||||
};
|
||||
|
||||
mod menu_def;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod speed_task;
|
||||
use menu_def::{MenuIds, MenuTexts};
|
||||
|
||||
// TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑)
|
||||
@ -45,6 +48,8 @@ enum IconKind {
|
||||
|
||||
pub struct Tray {
|
||||
limiter: SystemLimiter,
|
||||
#[cfg(target_os = "macos")]
|
||||
speed_controller: speed_task::TraySpeedController,
|
||||
}
|
||||
|
||||
impl TrayState {
|
||||
@ -113,6 +118,8 @@ impl Default for Tray {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
limiter: Limiter::new(Duration::from_millis(TRAY_CLICK_DEBOUNCE_MS), SystemClock),
|
||||
#[cfg(target_os = "macos")]
|
||||
speed_controller: speed_task::TraySpeedController::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -325,6 +332,8 @@ impl Tray {
|
||||
let verge = Config::verge().await.data_arc();
|
||||
self.update_menu().await?;
|
||||
self.update_icon(&verge).await?;
|
||||
#[cfg(target_os = "macos")]
|
||||
self.update_speed_task(verge.enable_tray_speed.unwrap_or(false));
|
||||
self.update_tooltip().await?;
|
||||
Ok(())
|
||||
}
|
||||
@ -382,6 +391,12 @@ impl Tray {
|
||||
}
|
||||
allow
|
||||
}
|
||||
|
||||
/// 根据配置统一更新托盘速率采集任务状态(macOS)
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn update_speed_task(&self, enable_tray_speed: bool) {
|
||||
self.speed_controller.update_task(enable_tray_speed);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_hotkeys(hotkeys: &Option<Vec<String>>) -> HashMap<String, String> {
|
||||
|
||||
194
src-tauri/src/core/tray/speed_task.rs
Normal file
194
src-tauri/src/core/tray/speed_task.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use crate::core::handle;
|
||||
use crate::process::AsyncHandler;
|
||||
use crate::utils::{connections_stream, tray_speed};
|
||||
use crate::{Type, logging};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::async_runtime::JoinHandle;
|
||||
use tauri_plugin_mihomo::models::ConnectionId;
|
||||
|
||||
/// 托盘速率流异常后的重连间隔。
|
||||
const TRAY_SPEED_RETRY_DELAY: Duration = Duration::from_secs(1);
|
||||
/// 托盘速率流运行时的空闲轮询间隔。
|
||||
const TRAY_SPEED_IDLE_POLL_INTERVAL: Duration = Duration::from_millis(200);
|
||||
/// 托盘速率流在此时间内收不到有效数据时,触发重连并降级到 0/0。
|
||||
const TRAY_SPEED_STALE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
/// macOS 托盘速率任务控制器。
|
||||
#[derive(Clone)]
|
||||
pub struct TraySpeedController {
|
||||
speed_task: Arc<Mutex<Option<JoinHandle<()>>>>,
|
||||
speed_connection_id: Arc<Mutex<Option<ConnectionId>>>,
|
||||
}
|
||||
|
||||
impl Default for TraySpeedController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
speed_task: Arc::new(Mutex::new(None)),
|
||||
speed_connection_id: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TraySpeedController {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn update_task(&self, enable_tray_speed: bool) {
|
||||
if enable_tray_speed {
|
||||
self.start_task();
|
||||
} else {
|
||||
self.stop_task();
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动托盘速率采集后台任务(基于 `/traffic` WebSocket 流)。
|
||||
fn start_task(&self) {
|
||||
if handle::Handle::global().is_exiting() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 关键步骤:托盘不可用时不启动速率任务,避免无效连接重试。
|
||||
if !Self::has_main_tray() {
|
||||
logging!(warn, Type::Tray, "托盘不可用,跳过启动托盘速率任务");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut guard = self.speed_task.lock();
|
||||
if guard.as_ref().is_some_and(|task| !task.inner().is_finished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let speed_connection_id = Arc::clone(&self.speed_connection_id);
|
||||
let task = AsyncHandler::spawn(move || async move {
|
||||
loop {
|
||||
if handle::Handle::global().is_exiting() {
|
||||
break;
|
||||
}
|
||||
|
||||
if !Self::has_main_tray() {
|
||||
logging!(warn, Type::Tray, "托盘已不可用,停止托盘速率任务");
|
||||
break;
|
||||
}
|
||||
|
||||
let stream_connect_result = connections_stream::connect_traffic_stream().await;
|
||||
let mut speed_stream = match stream_connect_result {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => {
|
||||
logging!(debug, Type::Tray, "托盘速率流连接失败,稍后重试: {err}");
|
||||
Self::apply_tray_speed(0, 0);
|
||||
tokio::time::sleep(TRAY_SPEED_RETRY_DELAY).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
Self::set_speed_connection_id(&speed_connection_id, Some(speed_stream.connection_id));
|
||||
|
||||
loop {
|
||||
let next_state = speed_stream
|
||||
.next_event(TRAY_SPEED_IDLE_POLL_INTERVAL, TRAY_SPEED_STALE_TIMEOUT, || {
|
||||
handle::Handle::global().is_exiting()
|
||||
})
|
||||
.await;
|
||||
|
||||
match next_state {
|
||||
connections_stream::StreamConsumeState::Event(speed_event) => {
|
||||
Self::apply_tray_speed(speed_event.up, speed_event.down);
|
||||
}
|
||||
connections_stream::StreamConsumeState::Stale => {
|
||||
logging!(debug, Type::Tray, "托盘速率流长时间未收到有效数据,触发重连");
|
||||
Self::apply_tray_speed(0, 0);
|
||||
break;
|
||||
}
|
||||
connections_stream::StreamConsumeState::Closed
|
||||
| connections_stream::StreamConsumeState::ExitRequested => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::disconnect_speed_connection(&speed_connection_id).await;
|
||||
|
||||
if handle::Handle::global().is_exiting() || !Self::has_main_tray() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stale 分支在内层 loop 中已重置为 0/0;此处兜底 Closed 分支(流被远端关闭)。
|
||||
Self::apply_tray_speed(0, 0);
|
||||
tokio::time::sleep(TRAY_SPEED_RETRY_DELAY).await;
|
||||
}
|
||||
|
||||
Self::set_speed_connection_id(&speed_connection_id, None);
|
||||
});
|
||||
|
||||
*guard = Some(task);
|
||||
}
|
||||
|
||||
/// 停止托盘速率采集后台任务并清除速率显示。
|
||||
fn stop_task(&self) {
|
||||
// 取出任务句柄,与 speed_connection_id 一同传入清理任务。
|
||||
let task = self.speed_task.lock().take();
|
||||
let speed_connection_id = Arc::clone(&self.speed_connection_id);
|
||||
|
||||
AsyncHandler::spawn(move || async move {
|
||||
// 关键步骤:先等待 abort 完成,再断开 WebSocket 连接。
|
||||
// 若直接 abort 后立即 disconnect,任务可能已通过 take 取走 connection_id
|
||||
// 但尚未完成断开,导致 connection_id 丢失、连接泄漏。
|
||||
// await task handle 可保证原任务已退出,connection_id 不再被占用。
|
||||
if let Some(task) = task {
|
||||
task.abort();
|
||||
let _ = task.await;
|
||||
}
|
||||
Self::disconnect_speed_connection(&speed_connection_id).await;
|
||||
});
|
||||
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
if let Some(tray) = app_handle.tray_by_id("main") {
|
||||
let result = tray.with_inner_tray_icon(|inner| {
|
||||
if let Some(status_item) = inner.ns_status_item() {
|
||||
tray_speed::clear_speed_attributed_title(&status_item);
|
||||
}
|
||||
});
|
||||
if let Err(err) = result {
|
||||
logging!(warn, Type::Tray, "清除富文本速率失败: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_main_tray() -> bool {
|
||||
handle::Handle::app_handle().tray_by_id("main").is_some()
|
||||
}
|
||||
|
||||
fn set_speed_connection_id(
|
||||
speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>,
|
||||
connection_id: Option<ConnectionId>,
|
||||
) {
|
||||
*speed_connection_id.lock() = connection_id;
|
||||
}
|
||||
|
||||
fn take_speed_connection_id(speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>) -> Option<ConnectionId> {
|
||||
speed_connection_id.lock().take()
|
||||
}
|
||||
|
||||
async fn disconnect_speed_connection(speed_connection_id: &Arc<Mutex<Option<ConnectionId>>>) {
|
||||
if let Some(connection_id) = Self::take_speed_connection_id(speed_connection_id) {
|
||||
connections_stream::disconnect_connection(connection_id).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_tray_speed(up: u64, down: u64) {
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
if let Some(tray) = app_handle.tray_by_id("main") {
|
||||
let result = tray.with_inner_tray_icon(move |inner| {
|
||||
if let Some(status_item) = inner.ns_status_item() {
|
||||
tray_speed::set_speed_attributed_title(&status_item, up, down);
|
||||
}
|
||||
});
|
||||
if let Err(err) = result {
|
||||
logging!(warn, Type::Tray, "设置富文本速率失败: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,10 @@ fn determine_update_flags(patch: &IVerge) -> UpdateFlags {
|
||||
let socks_port = patch.verge_socks_port;
|
||||
let http_enabled = patch.verge_http_enabled;
|
||||
let http_port = patch.verge_port;
|
||||
#[cfg(target_os = "macos")]
|
||||
let enable_tray_speed = patch.enable_tray_speed;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let enable_tray_speed: Option<bool> = None;
|
||||
// let enable_tray_icon = patch.enable_tray_icon;
|
||||
let enable_global_hotkey = patch.enable_global_hotkey;
|
||||
let tray_event = &patch.tray_event;
|
||||
@ -235,6 +238,10 @@ async fn process_terminated_flags(update_flags: UpdateFlags, patch: &IVerge) ->
|
||||
tray::Tray::global()
|
||||
.update_icon(&Config::verge().await.latest_arc())
|
||||
.await?;
|
||||
#[cfg(target_os = "macos")]
|
||||
if patch.enable_tray_speed.is_some() {
|
||||
tray::Tray::global().update_speed_task(patch.enable_tray_speed.unwrap_or(false));
|
||||
}
|
||||
}
|
||||
if update_flags.contains(UpdateFlags::SYSTRAY_TOOLTIP) {
|
||||
tray::Tray::global().update_tooltip().await?;
|
||||
|
||||
174
src-tauri/src/utils/connections_stream.rs
Normal file
174
src-tauri/src/utils/connections_stream.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use crate::{Type, core::handle, logging};
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
use tauri_plugin_mihomo::models::{ConnectionId, WebSocketMessage};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::Instant;
|
||||
|
||||
/// Mihomo WebSocket 流的有界队列容量,避免异常场景下内存无限增长。
|
||||
const MIHOMO_WS_STREAM_BUFFER_SIZE: usize = 8;
|
||||
/// 断开 Mihomo WebSocket 连接时使用的关闭码(RFC 6455 标准正常关闭)。
|
||||
const MIHOMO_WS_STREAM_CLOSE_CODE: u64 = 1000;
|
||||
|
||||
/// `/traffic` 即时速率事件(字节/秒)。
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TrafficSpeedEvent {
|
||||
pub up: u64,
|
||||
pub down: u64,
|
||||
}
|
||||
|
||||
/// Mihomo WebSocket 流消费状态。
|
||||
pub enum StreamConsumeState<T> {
|
||||
/// 收到一条业务事件。
|
||||
Event(T),
|
||||
/// 连接关闭或消息流结束。
|
||||
Closed,
|
||||
/// 在超时时间内未收到有效事件,需要重连。
|
||||
Stale,
|
||||
/// 上层请求退出消费循环。
|
||||
ExitRequested,
|
||||
}
|
||||
|
||||
enum InternalWsEvent<T> {
|
||||
Data(T),
|
||||
Closed,
|
||||
}
|
||||
|
||||
/// Mihomo WebSocket 订阅句柄(通用事件流)。
|
||||
pub struct MihomoWsEventStream<T> {
|
||||
/// 当前订阅连接 ID,用于主动断开。
|
||||
pub connection_id: ConnectionId,
|
||||
/// 当前订阅消息接收器。
|
||||
receiver: mpsc::Receiver<InternalWsEvent<T>>,
|
||||
/// 最近一次收到有效事件的时间戳。
|
||||
last_valid_event_at: Instant,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TrafficPayload {
|
||||
up: u64,
|
||||
down: u64,
|
||||
}
|
||||
|
||||
fn parse_traffic_event(data: Value) -> Option<InternalWsEvent<TrafficSpeedEvent>> {
|
||||
if let Ok(payload) = serde_json::from_value::<TrafficPayload>(data.clone()) {
|
||||
return Some(InternalWsEvent::Data(TrafficSpeedEvent {
|
||||
up: payload.up,
|
||||
down: payload.down,
|
||||
}));
|
||||
}
|
||||
|
||||
if let Ok(ws_message) = WebSocketMessage::deserialize(&data) {
|
||||
match ws_message {
|
||||
WebSocketMessage::Text(text) => {
|
||||
let payload = serde_json::from_str::<TrafficPayload>(&text).ok()?;
|
||||
Some(InternalWsEvent::Data(TrafficSpeedEvent {
|
||||
up: payload.up,
|
||||
down: payload.down,
|
||||
}))
|
||||
}
|
||||
WebSocketMessage::Close(_) => Some(InternalWsEvent::Closed),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn try_send_internal_event<T>(message_tx: &mpsc::Sender<InternalWsEvent<T>>, event: InternalWsEvent<T>) {
|
||||
if let Err(err) = message_tx.try_send(event) {
|
||||
match err {
|
||||
// 队列满时丢弃本次事件,下一次事件会继续覆盖更新。
|
||||
tokio::sync::mpsc::error::TrySendError::Full(_) => {}
|
||||
// 任务已结束时通道可能关闭,忽略即可。
|
||||
tokio::sync::mpsc::error::TrySendError::Closed(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 建立 `/traffic` WebSocket 订阅(通用流)。
|
||||
pub async fn connect_traffic_stream() -> Result<MihomoWsEventStream<TrafficSpeedEvent>> {
|
||||
// 使用有界 mpsc 通道承接回调事件,限制消息积压上限。
|
||||
let (message_tx, message_rx) = mpsc::channel::<InternalWsEvent<TrafficSpeedEvent>>(MIHOMO_WS_STREAM_BUFFER_SIZE);
|
||||
// 建立 Mihomo `/traffic` WebSocket 订阅。
|
||||
let connection_id = handle::Handle::mihomo()
|
||||
.await
|
||||
.ws_traffic({
|
||||
let message_tx = message_tx.clone();
|
||||
move |message| {
|
||||
if let Some(event) = parse_traffic_event(message) {
|
||||
try_send_internal_event(&message_tx, event);
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
drop(message_tx);
|
||||
Ok(MihomoWsEventStream {
|
||||
connection_id,
|
||||
receiver: message_rx,
|
||||
last_valid_event_at: Instant::now(),
|
||||
})
|
||||
}
|
||||
|
||||
impl<T> MihomoWsEventStream<T> {
|
||||
/// 等待下一次可用事件或结束状态。
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `idle_poll_interval` - 空闲检查间隔
|
||||
/// * `stale_timeout` - 无有效事件超时时间
|
||||
/// * `should_exit` - 上层退出判定函数
|
||||
pub async fn next_event<F>(
|
||||
&mut self,
|
||||
_idle_poll_interval: Duration, // 签名保留,但内部逻辑已进化为更高效的驱动方式
|
||||
stale_timeout: Duration,
|
||||
should_exit: F,
|
||||
) -> StreamConsumeState<T>
|
||||
where
|
||||
F: Fn() -> bool,
|
||||
{
|
||||
let sleep = tokio::time::sleep(stale_timeout);
|
||||
tokio::pin!(sleep);
|
||||
|
||||
loop {
|
||||
if should_exit() {
|
||||
return StreamConsumeState::ExitRequested;
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
maybe_event = self.receiver.recv() => {
|
||||
match maybe_event {
|
||||
Some(InternalWsEvent::Data(event)) => {
|
||||
self.last_valid_event_at = Instant::now();
|
||||
sleep.as_mut().reset(self.last_valid_event_at + stale_timeout);
|
||||
return StreamConsumeState::Event(event);
|
||||
}
|
||||
Some(InternalWsEvent::Closed) | None => return StreamConsumeState::Closed,
|
||||
}
|
||||
}
|
||||
_ = &mut sleep => {
|
||||
if self.last_valid_event_at.elapsed() >= stale_timeout {
|
||||
return StreamConsumeState::Stale;
|
||||
} else {
|
||||
sleep.as_mut().reset(self.last_valid_event_at + stale_timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 断开指定 Mihomo WebSocket 连接。
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `connection_id` - 目标连接 ID
|
||||
pub async fn disconnect_connection(connection_id: ConnectionId) {
|
||||
if let Err(err) = handle::Handle::mihomo()
|
||||
.await
|
||||
.disconnect(connection_id, Some(MIHOMO_WS_STREAM_CLOSE_CODE))
|
||||
.await
|
||||
{
|
||||
logging!(debug, Type::Tray, "断开 Mihomo WebSocket 连接失败: {err}");
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod connections_stream;
|
||||
pub mod dirs;
|
||||
pub mod help;
|
||||
pub mod init;
|
||||
@ -10,5 +12,8 @@ pub mod resolve;
|
||||
pub mod schtasks;
|
||||
pub mod server;
|
||||
pub mod singleton;
|
||||
pub mod speed;
|
||||
pub mod tmpl;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod tray_speed;
|
||||
pub mod window_manager;
|
||||
|
||||
71
src-tauri/src/utils/speed.rs
Normal file
71
src-tauri/src/utils/speed.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! 网络速率格式化工具
|
||||
|
||||
/// 速率显示升档阈值:保证显示值不超过三位数(显示层约定,与换算基数无关)
|
||||
const SPEED_DISPLAY_THRESHOLD: f64 = 1000.0;
|
||||
/// 速率展示单位顺序
|
||||
const SPEED_UNITS: [&str; 5] = ["B/s", "K/s", "M/s", "G/s", "T/s"];
|
||||
/// 预计算 1024 的幂次方,避免运行时重复计算 pow
|
||||
const SCALES: [f64; 5] = [
|
||||
1.0,
|
||||
1024.0,
|
||||
1024.0 * 1024.0,
|
||||
1024.0 * 1024.0 * 1024.0,
|
||||
1024.0 * 1024.0 * 1024.0 * 1024.0,
|
||||
];
|
||||
|
||||
/// 将字节/秒格式化为可读速率字符串
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `bytes_per_sec` - 每秒字节数
|
||||
pub fn format_bytes_per_second(bytes_per_sec: u64) -> String {
|
||||
if bytes_per_sec < SPEED_DISPLAY_THRESHOLD as u64 {
|
||||
return format!("{bytes_per_sec}B/s");
|
||||
}
|
||||
|
||||
let mut unit_index = (bytes_per_sec.ilog2() / 10) as usize;
|
||||
unit_index = unit_index.min(SPEED_UNITS.len() - 1);
|
||||
|
||||
let mut value = bytes_per_sec as f64 / SCALES[unit_index];
|
||||
|
||||
if value.round() >= SPEED_DISPLAY_THRESHOLD && unit_index < SPEED_UNITS.len() - 1 {
|
||||
unit_index += 1;
|
||||
value = bytes_per_sec as f64 / SCALES[unit_index];
|
||||
}
|
||||
|
||||
if value < 9.95 {
|
||||
format!("{value:.1}{}", SPEED_UNITS[unit_index])
|
||||
} else {
|
||||
format!("{:.0}{}", value.round(), SPEED_UNITS[unit_index])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::format_bytes_per_second;
|
||||
|
||||
#[test]
|
||||
fn format_handles_byte_boundaries() {
|
||||
assert_eq!(format_bytes_per_second(0), "0B/s");
|
||||
assert_eq!(format_bytes_per_second(999), "999B/s");
|
||||
// 1000 >= SPEED_DISPLAY_THRESHOLD,升档为 K/s(保证不超过三位数)
|
||||
assert_eq!(format_bytes_per_second(1000), "1.0K/s");
|
||||
assert_eq!(format_bytes_per_second(1024), "1.0K/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_handles_decimal_and_integer_rules() {
|
||||
assert_eq!(format_bytes_per_second(9 * 1024), "9.0K/s");
|
||||
// 9.999 K/s:rounded_1dp = 10.0,不满足 < 10,应显示整数 "10K/s"
|
||||
assert_eq!(format_bytes_per_second(10 * 1024 - 1), "10K/s");
|
||||
assert_eq!(format_bytes_per_second(10 * 1024), "10K/s");
|
||||
assert_eq!(format_bytes_per_second(123 * 1024), "123K/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_handles_unit_promotion_after_rounding() {
|
||||
// 999.5 K/s 四舍五入为 1000,≥ SPEED_DISPLAY_THRESHOLD,升档为 1.0M/s
|
||||
assert_eq!(format_bytes_per_second(999 * 1024 + 512), "1.0M/s");
|
||||
assert_eq!(format_bytes_per_second(1024 * 1024), "1.0M/s");
|
||||
assert_eq!(format_bytes_per_second(1536 * 1024), "1.5M/s");
|
||||
}
|
||||
}
|
||||
152
src-tauri/src/utils/tray_speed.rs
Normal file
152
src-tauri/src/utils/tray_speed.rs
Normal file
@ -0,0 +1,152 @@
|
||||
//! macOS 托盘速率富文本渲染模块
|
||||
//!
|
||||
//! 通过 objc2 调用 NSAttributedString 实现托盘速率的富文本显示,
|
||||
//! 支持等宽字体、自适应深色/浅色模式配色、两行定宽布局。
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::utils::speed::format_bytes_per_second;
|
||||
use crate::{Type, logging};
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2_app_kit::{
|
||||
NSBaselineOffsetAttributeName, NSColor, NSFont, NSFontAttributeName, NSFontWeightRegular,
|
||||
NSForegroundColorAttributeName, NSMutableParagraphStyle, NSParagraphStyleAttributeName, NSStatusItem,
|
||||
NSTextAlignment,
|
||||
};
|
||||
use objc2_foundation::{NSAttributedString, NSDictionary, NSNumber, NSString};
|
||||
|
||||
/// 富文本渲染使用的字号(适配两行在托盘栏的高度)
|
||||
const TRAY_FONT_SIZE: f64 = 9.5;
|
||||
/// 两行文本的行间距(负值可压缩两行高度,便于与图标纵向居中)
|
||||
const TRAY_LINE_SPACING: f64 = -1.0;
|
||||
/// 两行文本整体行高倍数(用于进一步压缩文本块高度)
|
||||
const TRAY_LINE_HEIGHT_MULTIPLE: f64 = 1.00;
|
||||
/// 文本块段前偏移(用于将两行文本整体下移)
|
||||
const TRAY_PARAGRAPH_SPACING_BEFORE: f64 = -5.0;
|
||||
/// 文字基线偏移(负值向下移动,更容易与托盘图标垂直居中)
|
||||
const TRAY_BASELINE_OFFSET: f64 = -4.0;
|
||||
|
||||
thread_local! {
|
||||
/// 托盘速率富文本属性字典(主线程缓存,避免每帧重建 ObjC 对象)。
|
||||
/// 仅在首次调用时初始化,后续复用同一实例。
|
||||
static TRAY_SPEED_ATTRS: Retained<NSDictionary<NSString, AnyObject>> = build_attributes();
|
||||
static LAST_DISPLAY_STR: RefCell<String> = RefCell::new(String::new());
|
||||
}
|
||||
|
||||
/// 将上行/下行速率格式化为两行定宽文本
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `up` - 上行速率(字节/秒)
|
||||
/// * `down` - 下行速率(字节/秒)
|
||||
fn format_tray_speed(up: u64, down: u64) -> String {
|
||||
// 上行放在第一行,下行放在第二行;通过上下布局表达方向,不再显示箭头字符。
|
||||
let up_str = format_bytes_per_second(up);
|
||||
let down_str = format_bytes_per_second(down);
|
||||
format!("{:>6}\n{:>6}", up_str, down_str)
|
||||
}
|
||||
|
||||
/// 构造带富文本样式属性的 NSDictionary
|
||||
///
|
||||
/// 包含:等宽字体、自适应标签颜色、右对齐段落样式
|
||||
fn build_attributes() -> Retained<NSDictionary<NSString, AnyObject>> {
|
||||
unsafe {
|
||||
// 等宽系统字体,确保数字不跳动
|
||||
let font = NSFont::monospacedSystemFontOfSize_weight(TRAY_FONT_SIZE, NSFontWeightRegular);
|
||||
// 自适应标签颜色(自动跟随深色/浅色模式)
|
||||
let color = NSColor::labelColor();
|
||||
// 段落样式:右对齐,保证定宽视觉一致
|
||||
let para_style = NSMutableParagraphStyle::new();
|
||||
para_style.setAlignment(NSTextAlignment::Right);
|
||||
para_style.setLineSpacing(TRAY_LINE_SPACING);
|
||||
para_style.setLineHeightMultiple(TRAY_LINE_HEIGHT_MULTIPLE);
|
||||
para_style.setParagraphSpacingBefore(TRAY_PARAGRAPH_SPACING_BEFORE);
|
||||
// 基线偏移:用于精确控制两行速率整体的纵向位置
|
||||
let baseline_offset = NSNumber::new_f64(TRAY_BASELINE_OFFSET);
|
||||
|
||||
let keys: &[&NSString] = &[
|
||||
NSFontAttributeName,
|
||||
NSForegroundColorAttributeName,
|
||||
NSParagraphStyleAttributeName,
|
||||
NSBaselineOffsetAttributeName,
|
||||
];
|
||||
let values: &[&AnyObject] = &[&font, &color, ¶_style, &baseline_offset];
|
||||
NSDictionary::from_slices(keys, values)
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建带属性的富文本
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `text` - 富文本字符串内容
|
||||
/// * `attrs` - 富文本属性字典
|
||||
fn create_attributed_string(
|
||||
text: &NSString,
|
||||
attrs: Option<&NSDictionary<NSString, AnyObject>>,
|
||||
) -> Retained<NSAttributedString> {
|
||||
unsafe {
|
||||
NSAttributedString::initWithString_attributes(<NSAttributedString as objc2::AnyThread>::alloc(), text, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
/// 在主线程下设置 NSStatusItem 按钮的富文本标题
|
||||
///
|
||||
/// 依赖 Tauri `with_inner_tray_icon` 保证回调在主线程执行;
|
||||
/// 若意外在非主线程调用,`MainThreadMarker::new()` 返回 `None` 并记录警告。
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `status_item` - macOS 托盘 NSStatusItem 引用
|
||||
/// * `text` - 富文本字符串内容
|
||||
/// * `attrs` - 富文本属性字典
|
||||
fn apply_status_item_attributed_title(
|
||||
status_item: &NSStatusItem,
|
||||
text: &NSString,
|
||||
attrs: Option<&NSDictionary<NSString, AnyObject>>,
|
||||
) {
|
||||
let Some(mtm) = MainThreadMarker::new() else {
|
||||
logging!(warn, Type::Tray, "托盘速率富文本设置跳过:非主线程调用");
|
||||
return;
|
||||
};
|
||||
let Some(button) = status_item.button(mtm) else {
|
||||
return;
|
||||
};
|
||||
let attr_str = create_attributed_string(text, attrs);
|
||||
button.setAttributedTitle(&attr_str);
|
||||
}
|
||||
|
||||
/// 将速率以富文本形式设置到 NSStatusItem 的按钮上
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `status_item` - macOS 托盘 NSStatusItem 引用
|
||||
/// * `up` - 上行速率(字节/秒)
|
||||
/// * `down` - 下行速率(字节/秒)
|
||||
pub fn set_speed_attributed_title(status_item: &NSStatusItem, up: u64, down: u64) {
|
||||
let speed_text = format_tray_speed(up, down);
|
||||
let changed = LAST_DISPLAY_STR.with(|last| {
|
||||
let mut last_borrow = last.borrow_mut();
|
||||
if *last_borrow == speed_text {
|
||||
false
|
||||
} else {
|
||||
*last_borrow = speed_text.clone();
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if !changed {
|
||||
return;
|
||||
}
|
||||
let ns_string = NSString::from_str(&speed_text);
|
||||
TRAY_SPEED_ATTRS.with(|attrs| {
|
||||
apply_status_item_attributed_title(status_item, &ns_string, Some(&**attrs));
|
||||
});
|
||||
}
|
||||
|
||||
/// 清除 NSStatusItem 按钮上的富文本速率显示
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `status_item` - macOS 托盘 NSStatusItem 引用
|
||||
pub fn clear_speed_attributed_title(status_item: &NSStatusItem) {
|
||||
let empty = NSString::from_str("");
|
||||
apply_status_item_attributed_title(status_item, &empty, None);
|
||||
}
|
||||
@ -405,9 +405,13 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
||||
</GuardState>
|
||||
</Item>
|
||||
)}
|
||||
{/* {OS === "macos" && (
|
||||
{OS === 'macos' && (
|
||||
<Item>
|
||||
<ListItemText primary={t("settings.components.verge.layout.fields.enableTraySpeed")} />
|
||||
<ListItemText
|
||||
primary={t(
|
||||
'settings.components.verge.layout.fields.enableTraySpeed',
|
||||
)}
|
||||
/>
|
||||
<GuardState
|
||||
value={verge?.enable_tray_speed ?? false}
|
||||
valueProps="checked"
|
||||
@ -419,7 +423,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
|
||||
<Switch edge="end" />
|
||||
</GuardState>
|
||||
</Item>
|
||||
)} */}
|
||||
)}
|
||||
{/* {OS === "macos" && (
|
||||
<Item>
|
||||
<ListItemText primary={t("settings.components.verge.layout.fields.enableTrayIcon")} />
|
||||
|
||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@ -912,7 +912,7 @@ interface IVergeConfig {
|
||||
common_tray_icon?: boolean
|
||||
sysproxy_tray_icon?: boolean
|
||||
tun_tray_icon?: boolean
|
||||
// enable_tray_speed?: boolean;
|
||||
enable_tray_speed?: boolean
|
||||
// enable_tray_icon?: boolean;
|
||||
tray_proxy_groups_display_mode?: 'default' | 'inline' | 'disable'
|
||||
tray_inline_outbound_modes?: boolean
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user