Update On Sun Jun 22 20:36:49 CEST 2025

This commit is contained in:
github-action[bot]
2025-06-22 20:36:50 +02:00
parent 640e4688d9
commit e8b78616b1
64 changed files with 1095 additions and 1445 deletions
+1
View File
@@ -1035,3 +1035,4 @@ Update On Wed Jun 18 20:39:43 CEST 2025
Update On Thu Jun 19 20:36:38 CEST 2025
Update On Fri Jun 20 20:38:08 CEST 2025
Update On Sat Jun 21 20:35:11 CEST 2025
Update On Sun Jun 22 20:36:41 CEST 2025
+26 -28
View File
@@ -1688,7 +1688,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -6526,9 +6526,9 @@ dependencies = [
[[package]]
name = "oxc_allocator"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e50218e74886659d1d13de8e6a4ff13c7e96924ed0017bc193a1feb8001b18"
checksum = "f4cb225affc487a1bc867455220d5427d0f2a35ed25d896f99bb3b912d49fb9e"
dependencies = [
"allocator-api2",
"bumpalo",
@@ -6539,12 +6539,11 @@ dependencies = [
[[package]]
name = "oxc_ast"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a2c546ff7887418facf35f7996f0ca2099bde75931597429b408746bbaaddcd"
checksum = "9ced8dcc14d588fa32594d70ff8f194712036d02d7a96718bce38abbfec72ed6"
dependencies = [
"bitflags 2.9.1",
"cow-utils",
"oxc_allocator",
"oxc_ast_macros",
"oxc_data_structures",
@@ -6556,9 +6555,9 @@ dependencies = [
[[package]]
name = "oxc_ast_macros"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ce8952bd09048ac55421aeecc06cc69db9f7220a25d69f3da8c6da3d95e5d6d"
checksum = "43494643bd6d76a62446c58ae98568bf630c0bdd90726d7956d3f8e1e17f5906"
dependencies = [
"phf 0.11.3",
"proc-macro2",
@@ -6568,9 +6567,9 @@ dependencies = [
[[package]]
name = "oxc_ast_visit"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a02f6d1e34a893acd0d6a0994ff56c861b08d472476c1bea95f9c04c6da3426"
checksum = "761d7f84b87080cf337c843fa60a1c3f50eb53eba0ec1e42d8758e99cd834031"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -6580,18 +6579,18 @@ dependencies = [
[[package]]
name = "oxc_data_structures"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caccf3f0c0515f32520b6207a0ef4bafd0858f94685e84a50f38c53464418e8b"
checksum = "1ebcebde288c0dbc9b8bb7ecf8d4eb3d64c6f122609fbca9f89dce356786fa19"
dependencies = [
"rustversion",
]
[[package]]
name = "oxc_diagnostics"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79d4d0062c704ee11dbd56d6178f91ea17ccac05a59fe655b120a2b64d038739"
checksum = "136bd60c8d25e6c1b879a01d5f52ad260385afa2d74b0aa81c1cbc9680b468ef"
dependencies = [
"cow-utils",
"oxc-miette",
@@ -6599,11 +6598,10 @@ dependencies = [
[[package]]
name = "oxc_ecmascript"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0e3cc7daeec1ed3ed75abc02a456d7046c5878c61bda972d26e39850f25298"
checksum = "405573ecc303a3e9852b2873aad30e8249b3e3cb668cf265f53a9072dada8d50"
dependencies = [
"cow-utils",
"num-bigint",
"num-traits",
"oxc_ast",
@@ -6613,9 +6611,9 @@ dependencies = [
[[package]]
name = "oxc_estree"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68bb8e3cedd84c69241a0438d14ca09294ab95049b52dc7813bba017554c96fb"
checksum = "bcd0073f1b212fda3c1e37728db069bf22d136a90f58da4f6214a6ada7552dde"
[[package]]
name = "oxc_index"
@@ -6625,9 +6623,9 @@ checksum = "2fa07b0cfa997730afed43705766ef27792873fdf5215b1391949fec678d2392"
[[package]]
name = "oxc_parser"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3bb3f8ed289796dcd7fecc4cfec895fbe29c269cf63658b63f352ce8fd7caf7"
checksum = "4c0b65b7f87759287bc7c8d394733cda9bca5e14fe7b71388932c926f8cde67b"
dependencies = [
"bitflags 2.9.1",
"cow-utils",
@@ -6648,9 +6646,9 @@ dependencies = [
[[package]]
name = "oxc_regular_expression"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e58c2970fa7dcb10a5c757706c5824c43f13abb908a4651b221033c96c1ddc"
checksum = "0899e918e7da993922ac9d85a7cacefc5519afbab002a4e239aa0a8dc2201297"
dependencies = [
"bitflags 2.9.1",
"oxc_allocator",
@@ -6664,9 +6662,9 @@ dependencies = [
[[package]]
name = "oxc_span"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f290a8f173016c5b84327297bb3f2b9c158b0733703ac2f29d9e77f9b4821ddd"
checksum = "d51599c317d4e19c07978bde4af8a40844a2410fb7b455836c3441a41865acfa"
dependencies = [
"compact_str",
"oxc-miette",
@@ -6677,9 +6675,9 @@ dependencies = [
[[package]]
name = "oxc_syntax"
version = "0.72.3"
version = "0.73.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d83f7a8a718db14fd6e8f864b70b0961e3b4ae44c9a28102262357c27aee944"
checksum = "1ee16107642e60a1f53dac2dbaaf1a1cdc696c0f7427946f8b8906f3e09a85f0"
dependencies = [
"bitflags 2.9.1",
"cow-utils",
@@ -11222,7 +11220,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
+6 -6
View File
@@ -172,12 +172,12 @@ display-info = "0.5.0" # should be removed after upgrading to tauri v2
# OXC (The Oxidation Compiler)
# We use it to parse and transpile the old script profile to esm based script profile
oxc_parser = "0.72"
oxc_allocator = "0.72"
oxc_span = "0.72"
oxc_ast = "0.72"
oxc_syntax = "0.72"
oxc_ast_visit = "0.72"
oxc_parser = "0.73"
oxc_allocator = "0.73"
oxc_span = "0.73"
oxc_ast = "0.73"
oxc_syntax = "0.73"
oxc_ast_visit = "0.73"
# Lua Integration
mlua = { version = "0.10", features = [
@@ -11,7 +11,7 @@
"build": "tsc"
},
"dependencies": {
"@tanstack/react-query": "5.80.10",
"@tanstack/react-query": "5.81.2",
"@tauri-apps/api": "2.5.0",
"ahooks": "3.8.5",
"dayjs": "1.11.13",
@@ -57,7 +57,7 @@
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.351",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.80.10",
"@tanstack/react-query": "5.81.2",
"@tanstack/react-router": "1.121.27",
"@tanstack/react-router-devtools": "1.121.27",
"@tanstack/router-plugin": "1.121.29",
+2 -2
View File
@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.10",
"mihomo_alpha": "alpha-c60750d",
"mihomo_alpha": "alpha-5344e86",
"clash_rs": "v0.8.0",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.8.0-alpha+sha.7af693b"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-06-19T22:21:06.904Z"
"updated_at": "2025-06-21T22:20:50.601Z"
}
+11 -11
View File
@@ -173,8 +173,8 @@ importers:
frontend/interface:
dependencies:
'@tanstack/react-query':
specifier: 5.80.10
version: 5.80.10(react@19.1.0)
specifier: 5.81.2
version: 5.81.2(react@19.1.0)
'@tauri-apps/api':
specifier: 2.5.0
version: 2.5.0
@@ -343,8 +343,8 @@ importers:
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-query':
specifier: 5.80.10
version: 5.80.10(react@19.1.0)
specifier: 5.81.2
version: 5.81.2(react@19.1.0)
'@tanstack/react-router':
specifier: 1.121.27
version: 1.121.27(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -2853,11 +2853,11 @@ packages:
resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==}
engines: {node: '>=12'}
'@tanstack/query-core@5.80.10':
resolution: {integrity: sha512-mUNQOtzxkjL6jLbyChZoSBP6A5gQDVRUiPvW+/zw/9ftOAz+H754zCj3D8PwnzPKyHzGkQ9JbH48ukhym9LK1Q==}
'@tanstack/query-core@5.81.2':
resolution: {integrity: sha512-QLYkPdrudoMATDFa3MiLEwRhNnAlzHWDf0LKaXUqJd0/+QxN8uTPi7bahRlxoAyH0UbLMBdeDbYzWALj7THOtw==}
'@tanstack/react-query@5.80.10':
resolution: {integrity: sha512-6zM098J8sLy9oU60XAdzUlAH4wVzoMVsWUWiiE/Iz4fd67PplxeyL4sw/MPcVJJVhbwGGXCsHn9GrQt2mlAzig==}
'@tanstack/react-query@5.81.2':
resolution: {integrity: sha512-pe8kFlTrL2zFLlcAj2kZk9UaYYHDk9/1hg9EBaoO3cxDhOZf1FRGJeziSXKrVZyxIfs7b3aoOj/bw7Lie0mDUg==}
peerDependencies:
react: ^18 || ^19
@@ -10780,11 +10780,11 @@ snapshots:
dependencies:
remove-accents: 0.5.0
'@tanstack/query-core@5.80.10': {}
'@tanstack/query-core@5.81.2': {}
'@tanstack/react-query@5.80.10(react@19.1.0)':
'@tanstack/react-query@5.81.2(react@19.1.0)':
dependencies:
'@tanstack/query-core': 5.80.10
'@tanstack/query-core': 5.81.2
react: 19.1.0
'@tanstack/react-router-devtools@1.121.27(@tanstack/react-router@1.121.27(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.121.27)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
+7 -1
View File
@@ -3,17 +3,23 @@
### 🐞 修复问题
- 修复系统代理端口不同步问题
- 修复 自定义 `css` 背景图无法生效问题
- 修复自定义 `css` 背景图无法生效问题
- 修复在轻量模式下快速点击托盘图标带来的竞争态卡死问题
### ✨ 新增功能
- `sidecar` 模式下清理多余的内核进程,防止运行出现异常
- 新 macOS 下 TUN 和系统代理模式托盘图标(暂测)
### 🚀 优化改进
- 优化重构订阅切换逻辑,可以随时中断载入过程,防止卡死
- 引入事件驱动代理管理器,优化代理配置更新逻辑,防止卡死
### 🗑️ 移除内容
- 移除了 macOS tray 图标显示网络速率
## v2.3.1
### 🐞 修复问题
+6 -12
View File
@@ -31,10 +31,10 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^7.1.1",
"@mui/lab": "7.0.0-beta.13",
"@mui/material": "^7.1.1",
"@mui/x-data-grid": "^8.5.2",
"@mui/icons-material": "^7.1.2",
"@mui/lab": "7.0.0-beta.14",
"@mui/material": "^7.1.2",
"@mui/x-data-grid": "^8.5.3",
"@tauri-apps/api": "2.5.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.3",
"@tauri-apps/plugin-dialog": "^2.2.2",
@@ -45,24 +45,21 @@
"@tauri-apps/plugin-shell": "2.2.2",
"@tauri-apps/plugin-updater": "2.8.1",
"@tauri-apps/plugin-window-state": "^2.2.3",
"@types/d3-shape": "^3.1.7",
"@types/json-schema": "^7.0.15",
"json-schema": "^0.4.0",
"ahooks": "^3.8.5",
"axios": "^1.10.0",
"chart.js": "^4.5.0",
"cli-color": "^2.0.4",
"d3-shape": "^3.2.0",
"dayjs": "1.11.13",
"foxact": "^0.2.49",
"glob": "^11.0.3",
"i18next": "^25.2.1",
"js-base64": "^3.7.7",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.52.2",
"monaco-yaml": "^5.4.0",
"nanoid": "^5.1.5",
"peggy": "^5.0.3",
"react": "19.1.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "19.1.0",
@@ -82,7 +79,6 @@
"devDependencies": {
"@actions/github": "^6.0.1",
"@tauri-apps/cli": "2.5.0",
"@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/react": "19.1.8",
@@ -93,13 +89,11 @@
"commander": "^14.0.0",
"cross-env": "^7.0.3",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"meta-json-schema": "^1.19.10",
"node-fetch": "^3.3.2",
"prettier": "^3.5.3",
"pretty-quick": "^4.2.2",
"sass": "^1.89.2",
"terser": "^5.43.0",
"terser": "^5.43.1",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-monaco-editor": "^1.1.0",
+59 -188
View File
@@ -27,17 +27,17 @@ importers:
specifier: ^3.4.0
version: 3.4.0
'@mui/icons-material':
specifier: ^7.1.1
version: 7.1.1(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
specifier: ^7.1.2
version: 7.1.2(@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@mui/lab':
specifier: 7.0.0-beta.13
version: 7.0.0-beta.13(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: 7.0.0-beta.14
version: 7.0.0-beta.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/material':
specifier: ^7.1.1
version: 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: ^7.1.2
version: 7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/x-data-grid':
specifier: ^8.5.2
version: 8.5.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: ^8.5.3
version: 8.5.3(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tauri-apps/api':
specifier: 2.5.0
version: 2.5.0
@@ -68,9 +68,6 @@ importers:
'@tauri-apps/plugin-window-state':
specifier: ^2.2.3
version: 2.2.3
'@types/d3-shape':
specifier: ^3.1.7
version: 3.1.7
'@types/json-schema':
specifier: ^7.0.15
version: 7.0.15
@@ -86,9 +83,6 @@ importers:
cli-color:
specifier: ^2.0.4
version: 2.0.4
d3-shape:
specifier: ^3.2.0
version: 3.2.0
dayjs:
specifier: 1.11.13
version: 1.11.13
@@ -101,12 +95,12 @@ importers:
i18next:
specifier: ^25.2.1
version: 25.2.1(typescript@5.8.3)
js-base64:
specifier: ^3.7.7
version: 3.7.7
js-yaml:
specifier: ^4.1.0
version: 4.1.0
json-schema:
specifier: ^0.4.0
version: 0.4.0
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@@ -119,9 +113,6 @@ importers:
nanoid:
specifier: ^5.1.5
version: 5.1.5
peggy:
specifier: ^5.0.3
version: 5.0.3
react:
specifier: 19.1.0
version: 19.1.0
@@ -174,9 +165,6 @@ importers:
'@tauri-apps/cli':
specifier: 2.5.0
version: 2.5.0
'@types/js-cookie':
specifier: ^3.0.6
version: 3.0.6
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
@@ -191,10 +179,10 @@ importers:
version: 19.1.6(@types/react@19.1.8)
'@vitejs/plugin-legacy':
specifier: ^6.1.1
version: 6.1.1(terser@5.43.0)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))
version: 6.1.1(terser@5.43.1)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))
'@vitejs/plugin-react':
specifier: 4.5.2
version: 4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))
version: 4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))
adm-zip:
specifier: ^0.5.16
version: 0.5.16
@@ -207,9 +195,6 @@ importers:
https-proxy-agent:
specifier: ^7.0.6
version: 7.0.6
husky:
specifier: ^9.1.7
version: 9.1.7
meta-json-schema:
specifier: ^1.19.10
version: 1.19.10
@@ -219,27 +204,24 @@ importers:
prettier:
specifier: ^3.5.3
version: 3.5.3
pretty-quick:
specifier: ^4.2.2
version: 4.2.2(prettier@3.5.3)
sass:
specifier: ^1.89.2
version: 1.89.2
terser:
specifier: ^5.43.0
version: 5.43.0
specifier: ^5.43.1
version: 5.43.1
typescript:
specifier: ^5.8.3
version: 5.8.3
vite:
specifier: ^6.3.5
version: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
version: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)
vite-plugin-monaco-editor:
specifier: ^1.1.0
version: 1.1.0(monaco-editor@0.52.2)
vite-plugin-svgr:
specifier: ^4.3.0
version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))
version: 4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))
packages:
@@ -1023,27 +1005,27 @@ packages:
'@kurkle/color@0.3.4':
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
'@mui/core-downloads-tracker@7.1.1':
resolution: {integrity: sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==}
'@mui/core-downloads-tracker@7.1.2':
resolution: {integrity: sha512-0gLO1PvbJwSYe5ji021tGj6HFqrtEPMGKK4L1zWwRbhzrWWUumUJvMvJUsIgWQIYQsgOnhq9k2Fc1BxLGHDsAg==}
'@mui/icons-material@7.1.1':
resolution: {integrity: sha512-X37+Yc8QpEnl0sYmz+WcLFy2dWgNRzbswDzLPXG7QU1XDVlP5TPp1HXjdmCupOWLL/I9m1fyhcyZl8/HPpp/Cg==}
'@mui/icons-material@7.1.2':
resolution: {integrity: sha512-slqJByDub7Y1UcokrM17BoMBMvn8n7daXFXVoTv0MEH5k3sHjmsH8ql/Mt3s9vQ20cORDr83UZ448TEGcbrXtw==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@mui/material': ^7.1.1
'@mui/material': ^7.1.2
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@mui/lab@7.0.0-beta.13':
resolution: {integrity: sha512-wLSeePenug3+/kek4cFMIF3QZVC2fHt2Z3O3HwOFvakgErmT39WltYsNpWNojCnXUqcIExUp9xNW0Wk+tJShgA==}
'@mui/lab@7.0.0-beta.14':
resolution: {integrity: sha512-pn+ZvylDcBKQOo17oa/PhtIA/UFQFq8RvpN+r/jHrztz/CjMDju2CWBne0txvQ5JIS8uTIGp2/IsTa7II1g5wg==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
'@mui/material': ^7.1.1
'@mui/material': ^7.1.2
'@mui/material-pigment-css': ^7.1.1
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -1058,8 +1040,8 @@ packages:
'@types/react':
optional: true
'@mui/material@7.1.1':
resolution: {integrity: sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==}
'@mui/material@7.1.2':
resolution: {integrity: sha512-Z5PYKkA6Kd8vS04zKxJNpwuvt6IoMwqpbidV7RCrRQQKwczIwcNcS8L6GnN4pzFYfEs+N9v6co27DmG07rcnoA==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@@ -1135,8 +1117,8 @@ packages:
'@types/react':
optional: true
'@mui/x-data-grid@8.5.2':
resolution: {integrity: sha512-4KzawLZqRKp3KcGKsTDVz7zkEjACllQD5Zb8ds1QKlA6C3/oIoSU7PsemFLj+RL3rT5aORsLMBl97/egQ5tUhA==}
'@mui/x-data-grid@8.5.3':
resolution: {integrity: sha512-rA+de5yre16KFIGKRBUwb8kYIdn7SPPrZsBy1P3QxisqhC+Wz2AQg/W6WWv71aFHwplmGwsFUjU6d47Fy/wvXg==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.9.0
@@ -1151,8 +1133,8 @@ packages:
'@emotion/styled':
optional: true
'@mui/x-internals@8.5.2':
resolution: {integrity: sha512-5YhB2AekK7G8d0YrAjg3WNf0uy3V73JD98WNxJhbIlCraQgl8QOQzr2zNO7MAf/X7mZQtjpjuAsiG3+gI2NVyg==}
'@mui/x-internals@8.5.3':
resolution: {integrity: sha512-ImCg4E3DT3XoDIZO0pNCbB7iw14N+YCFY3J1V28POwCD7P2f3HSIz4jwzM006oYxI6bqeE6LMfpdPRDW6s6dQw==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
@@ -1288,14 +1270,6 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@peggyjs/from-mem@2.0.0':
resolution: {integrity: sha512-f+pL/s2DiT+2dxwheSoJT0P/KJy/s0klzE+ZqRdXHlkeyFk/DpKtyjLZIiA79kx56g3oEPA8Zu9EzEKzAwuvhw==}
engines: {node: '>=20'}
'@pkgr/core@0.2.7':
resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -1592,12 +1566,6 @@ packages:
'@types/babel__traverse@7.20.7':
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
'@types/d3-path@3.1.1':
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
'@types/d3-shape@3.1.7':
resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -1610,9 +1578,6 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/js-cookie@3.0.6':
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
@@ -1875,14 +1840,6 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
d3-path@3.1.0:
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
engines: {node: '>=12'}
d3-shape@3.2.0:
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
engines: {node: '>=12'}
d@1.0.2:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
@@ -2132,11 +2089,6 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
husky@9.1.7:
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
engines: {node: '>=18'}
hasBin: true
i18next@25.2.1:
resolution: {integrity: sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==}
peerDependencies:
@@ -2145,10 +2097,6 @@ packages:
typescript:
optional: true
ignore@7.0.5:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
immutable@5.1.2:
resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==}
@@ -2211,9 +2159,6 @@ packages:
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
engines: {node: 20 || >=22}
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -2238,6 +2183,9 @@ packages:
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -2435,10 +2383,6 @@ packages:
peerDependencies:
monaco-editor: '>=0.36'
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -2512,11 +2456,6 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
peggy@5.0.3:
resolution: {integrity: sha512-QErYmLjj/ehiNNJRqx2qb36hzkanuascpMqREs2RQqaXhU3cflIRScP/u2BoobIfu/FaeI3GGxNB/vFX/Ar9lg==}
engines: {node: '>=20'}
hasBin: true
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2537,13 +2476,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
pretty-quick@4.2.2:
resolution: {integrity: sha512-uAh96tBW1SsD34VhhDmWuEmqbpfYc/B3j++5MC/6b3Cb8Ow7NJsvKFhg0eoGu2xXX+o9RkahkTK6sUdd8E7g5w==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
prettier: ^3.0.0
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -2717,11 +2649,6 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
semver@7.7.1:
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
engines: {node: '>=10'}
hasBin: true
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
@@ -2746,10 +2673,6 @@ packages:
sockette@2.0.6:
resolution: {integrity: sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q==}
source-map-generator@2.0.0:
resolution: {integrity: sha512-4KomB7QsJti7dFBAVF6SXHzuCNQauk4gE2CummcqPzl+eJqXz1CkkiBdVXXW3g8VGh23bxcdEVACOzrxpIqnUg==}
engines: {node: '>=20'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -2815,8 +2738,8 @@ packages:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'}
terser@5.43.0:
resolution: {integrity: sha512-CqNNxKSGKSZCunSvwKLTs8u8sGGlp27sxNZ4quGh0QeNuyHM0JSEM/clM9Mf4zUp6J+tO2gUXhgXT2YMMkwfKQ==}
terser@5.43.1:
resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==}
engines: {node: '>=10'}
hasBin: true
@@ -2824,9 +2747,6 @@ packages:
resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==}
engines: {node: '>=0.12'}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'}
@@ -3943,20 +3863,20 @@ snapshots:
'@kurkle/color@0.3.4': {}
'@mui/core-downloads-tracker@7.1.1': {}
'@mui/core-downloads-tracker@7.1.2': {}
'@mui/icons-material@7.1.1(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
'@mui/icons-material@7.1.2(@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
dependencies:
'@babel/runtime': 7.27.6
'@mui/material': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/material': 7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@mui/lab@7.0.0-beta.13(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
'@mui/lab@7.0.0-beta.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@babel/runtime': 7.27.6
'@mui/material': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/material': 7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@mui/types': 7.4.3(@types/react@19.1.8)
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
@@ -3969,10 +3889,10 @@ snapshots:
'@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@types/react': 19.1.8
'@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
'@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@babel/runtime': 7.27.6
'@mui/core-downloads-tracker': 7.1.1
'@mui/core-downloads-tracker': 7.1.2
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@mui/types': 7.4.3(@types/react@19.1.8)
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
@@ -4046,13 +3966,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.8
'@mui/x-data-grid@8.5.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
'@mui/x-data-grid@8.5.3(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@mui/material@7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@babel/runtime': 7.27.6
'@mui/material': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/material': 7.1.2(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@mui/utils': 7.1.1(@types/react@19.1.8)(react@19.1.0)
'@mui/x-internals': 8.5.2(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
'@mui/x-internals': 8.5.3(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
clsx: 2.1.1
prop-types: 15.8.1
react: 19.1.0
@@ -4064,7 +3984,7 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
'@mui/x-internals@8.5.2(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
'@mui/x-internals@8.5.3(@mui/system@7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)':
dependencies:
'@babel/runtime': 7.27.6
'@mui/system': 7.1.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)
@@ -4193,12 +4113,6 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.1
optional: true
'@peggyjs/from-mem@2.0.0':
dependencies:
semver: 7.7.1
'@pkgr/core@0.2.7': {}
'@popperjs/core@2.11.8': {}
'@rolldown/pluginutils@1.0.0-beta.11': {}
@@ -4447,12 +4361,6 @@ snapshots:
dependencies:
'@babel/types': 7.27.6
'@types/d3-path@3.1.1': {}
'@types/d3-shape@3.1.7':
dependencies:
'@types/d3-path': 3.1.1
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -4467,8 +4375,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/js-cookie@3.0.6': {}
'@types/js-yaml@4.0.9': {}
'@types/json-schema@7.0.15': {}
@@ -4507,7 +4413,7 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-legacy@6.1.1(terser@5.43.0)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))':
'@vitejs/plugin-legacy@6.1.1(terser@5.43.1)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))':
dependencies:
'@babel/core': 7.27.4
'@babel/preset-env': 7.27.2(@babel/core@7.27.4)
@@ -4517,12 +4423,12 @@ snapshots:
magic-string: 0.30.17
regenerator-runtime: 0.14.1
systemjs: 6.15.1
terser: 5.43.0
vite: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
terser: 5.43.1
vite: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-react@4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1))':
'@vitejs/plugin-react@4.5.2(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1))':
dependencies:
'@babel/core': 7.27.4
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4)
@@ -4530,7 +4436,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.11
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
vite: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
vite: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)
transitivePeerDependencies:
- supports-color
@@ -4728,12 +4634,6 @@ snapshots:
csstype@3.1.3: {}
d3-path@3.1.0: {}
d3-shape@3.2.0:
dependencies:
d3-path: 3.1.0
d@1.0.2:
dependencies:
es5-ext: 0.10.64
@@ -5020,16 +4920,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
husky@9.1.7: {}
i18next@25.2.1(typescript@5.8.3):
dependencies:
'@babel/runtime': 7.27.6
optionalDependencies:
typescript: 5.8.3
ignore@7.0.5: {}
immutable@5.1.2: {}
import-fresh@3.3.1:
@@ -5081,8 +4977,6 @@ snapshots:
dependencies:
'@isaacs/cliui': 8.0.2
js-base64@3.7.7: {}
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
@@ -5097,6 +4991,8 @@ snapshots:
json-parse-even-better-errors@2.3.1: {}
json-schema@0.4.0: {}
json5@2.2.3: {}
jsonc-parser@3.3.1: {}
@@ -5429,8 +5325,6 @@ snapshots:
vscode-uri: 3.1.0
yaml: 2.7.1
mri@1.2.0: {}
ms@2.1.3: {}
nanoid@3.3.11: {}
@@ -5499,12 +5393,6 @@ snapshots:
path-type@4.0.0: {}
peggy@5.0.3:
dependencies:
'@peggyjs/from-mem': 2.0.0
commander: 14.0.0
source-map-generator: 2.0.0
picocolors@1.1.1: {}
picomatch@2.3.1:
@@ -5520,17 +5408,6 @@ snapshots:
prettier@3.5.3: {}
pretty-quick@4.2.2(prettier@3.5.3):
dependencies:
'@pkgr/core': 0.2.7
ignore: 7.0.5
mri: 1.2.0
picocolors: 1.1.1
picomatch: 4.0.2
prettier: 3.5.3
tinyexec: 0.3.2
tslib: 2.8.1
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -5726,8 +5603,6 @@ snapshots:
semver@6.3.1: {}
semver@7.7.1: {}
server-only@0.0.1: {}
set-cookie-parser@2.7.1: {}
@@ -5747,8 +5622,6 @@ snapshots:
sockette@2.0.6: {}
source-map-generator@2.0.0: {}
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -5818,7 +5691,7 @@ snapshots:
mkdirp: 3.0.1
yallist: 5.0.0
terser@5.43.0:
terser@5.43.1:
dependencies:
'@jridgewell/source-map': 0.3.6
acorn: 8.14.1
@@ -5830,8 +5703,6 @@ snapshots:
es5-ext: 0.10.64
next-tick: 1.1.0
tinyexec@0.3.2: {}
tinyglobby@0.2.13:
dependencies:
fdir: 6.4.4(picomatch@4.0.2)
@@ -5930,18 +5801,18 @@ snapshots:
dependencies:
monaco-editor: 0.52.2
vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)):
vite-plugin-svgr@4.3.0(rollup@4.40.2)(typescript@5.8.3)(vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)):
dependencies:
'@rollup/pluginutils': 5.1.4(rollup@4.40.2)
'@svgr/core': 8.1.0(typescript@5.8.3)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
vite: 6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1)
vite: 6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
vite@6.3.5(sass@1.89.2)(terser@5.43.0)(yaml@2.7.1):
vite@6.3.5(sass@1.89.2)(terser@5.43.1)(yaml@2.7.1):
dependencies:
esbuild: 0.25.4
fdir: 6.4.4(picomatch@4.0.2)
@@ -5952,7 +5823,7 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
sass: 1.89.2
terser: 5.43.0
terser: 5.43.1
yaml: 2.7.1
void-elements@3.1.0: {}
+1
View File
@@ -1078,6 +1078,7 @@ dependencies = [
"reqwest",
"reqwest_dav",
"runas",
"scopeguard",
"serde",
"serde_json",
"serde_yaml",
+5
View File
@@ -80,6 +80,7 @@ gethostname = "1.0.2"
hmac = "0.12.1"
sha2 = "0.10.9"
hex = "0.4.3"
scopeguard = "1.2.0"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
@@ -93,6 +94,10 @@ winapi = { version = "0.3.9", features = [
"errhandlingapi",
"minwindef",
"winerror",
"tlhelp32",
"processthreadsapi",
"winhttp",
"winreg",
] }
[target.'cfg(target_os = "linux")'.dependencies]
Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

@@ -1,10 +1,12 @@
#[cfg(target_os = "linux")]
use anyhow::anyhow;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::process::Command;
use tokio::time::{timeout, Duration};
#[cfg(target_os = "linux")]
use anyhow::anyhow;
#[cfg(not(target_os = "windows"))]
use tokio::process::Command;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AsyncAutoproxy {
pub enable: bool,
@@ -71,54 +73,96 @@ impl AsyncProxyQuery {
#[cfg(target_os = "windows")]
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
// Windows: 使用 netsh winhttp show proxy 命令
let output = Command::new("netsh")
.args(["winhttp", "show", "proxy"])
.output()
.await?;
// Windows: 从注册表读取PAC配置
tokio::task::spawn_blocking(move || -> Result<AsyncAutoproxy> {
Self::get_pac_config_from_registry()
})
.await?
}
if !output.status.success() {
return Ok(AsyncAutoproxy::default());
}
#[cfg(target_os = "windows")]
fn get_pac_config_from_registry() -> Result<AsyncAutoproxy> {
use std::ptr;
use winapi::shared::minwindef::{DWORD, HKEY};
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
let stdout = String::from_utf8_lossy(&output.stdout);
log::debug!(target: "app", "netsh output: {}", stdout);
unsafe {
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
.encode_utf16()
.collect::<Vec<u16>>();
// 解析输出,查找 PAC 配置
for line in stdout.lines() {
let line = line.trim();
if line.starts_with("代理自动配置脚本") || line.starts_with("Proxy auto-config script")
{
// 修复:正确解析包含冒号的URL
// 格式: "代理自动配置脚本 : http://127.0.0.1:11233/commands/pac"
// 或: "Proxy auto-config script : http://127.0.0.1:11233/commands/pac"
if let Some(colon_pos) = line.find(" : ") {
let url = line[colon_pos + 3..].trim();
if !url.is_empty() && url != "(none)" && url != "" {
log::debug!(target: "app", "解析到PAC URL: {}", url);
return Ok(AsyncAutoproxy {
enable: true,
url: url.to_string(),
});
}
} else if let Some(colon_pos) = line.find(':') {
// 兼容其他可能的格式
let url = line[colon_pos + 1..].trim();
// 确保这不是URL中的协议部分
if url.starts_with("http") && !url.is_empty() && url != "(none)" && url != ""
{
log::debug!(target: "app", "解析到PAC URL (fallback): {}", url);
return Ok(AsyncAutoproxy {
enable: true,
url: url.to_string(),
});
}
let mut hkey: HKEY = ptr::null_mut();
let result =
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
if result != 0 {
log::debug!(target: "app", "无法打开注册表项");
return Ok(AsyncAutoproxy::default());
}
// 1. 检查自动配置是否启用 (AutoConfigURL 存在且不为空即表示启用)
let auto_config_url_name = "AutoConfigURL\0".encode_utf16().collect::<Vec<u16>>();
let mut url_buffer = vec![0u16; 1024];
let mut url_buffer_size: DWORD = (url_buffer.len() * 2) as DWORD;
let mut url_value_type: DWORD = 0;
let url_query_result = RegQueryValueExW(
hkey,
auto_config_url_name.as_ptr(),
ptr::null_mut(),
&mut url_value_type,
url_buffer.as_mut_ptr() as *mut u8,
&mut url_buffer_size,
);
let mut pac_url = String::new();
if url_query_result == 0 && url_value_type == REG_SZ && url_buffer_size > 0 {
let end_pos = url_buffer
.iter()
.position(|&x| x == 0)
.unwrap_or(url_buffer.len());
pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]);
log::debug!(target: "app", "从注册表读取到PAC URL: {}", pac_url);
}
// 2. 检查自动检测设置是否启用
let auto_detect_name = "AutoDetect\0".encode_utf16().collect::<Vec<u16>>();
let mut auto_detect: DWORD = 0;
let mut detect_buffer_size: DWORD = 4;
let mut detect_value_type: DWORD = 0;
let detect_query_result = RegQueryValueExW(
hkey,
auto_detect_name.as_ptr(),
ptr::null_mut(),
&mut detect_value_type,
&mut auto_detect as *mut DWORD as *mut u8,
&mut detect_buffer_size,
);
RegCloseKey(hkey);
// PAC 启用的条件:AutoConfigURL 不为空,或 AutoDetect 被启用
let pac_enabled = !pac_url.is_empty()
|| (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0);
if pac_enabled {
log::debug!(target: "app", "PAC配置启用: URL={}, AutoDetect={}", pac_url, auto_detect);
if pac_url.is_empty() && auto_detect != 0 {
pac_url = "auto-detect".to_string();
}
Ok(AsyncAutoproxy {
enable: true,
url: pac_url,
})
} else {
log::debug!(target: "app", "PAC配置未启用");
Ok(AsyncAutoproxy::default())
}
}
log::debug!(target: "app", "未找到有效的PAC配置");
Ok(AsyncAutoproxy::default())
}
#[cfg(target_os = "macos")]
@@ -142,7 +186,7 @@ impl AsyncProxyQuery {
if line.contains("ProxyAutoConfigEnable") && line.contains("1") {
pac_enabled = true;
} else if line.contains("ProxyAutoConfigURLString") {
// 修复:正确解析包含冒号的URL
// 正确解析包含冒号的URL
// 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac"
if let Some(colon_pos) = line.find(" : ") {
pac_url = line[colon_pos + 3..].trim().to_string();
@@ -213,57 +257,121 @@ impl AsyncProxyQuery {
#[cfg(target_os = "windows")]
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
let output = Command::new("netsh")
.args(["winhttp", "show", "proxy"])
.output()
.await?;
// Windows: 使用注册表直接读取代理设置
tokio::task::spawn_blocking(move || -> Result<AsyncSysproxy> {
Self::get_system_proxy_from_registry()
})
.await?
}
if !output.status.success() {
return Ok(AsyncSysproxy::default());
}
#[cfg(target_os = "windows")]
fn get_system_proxy_from_registry() -> Result<AsyncSysproxy> {
use std::ptr;
use winapi::shared::minwindef::{DWORD, HKEY};
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
let stdout = String::from_utf8_lossy(&output.stdout);
log::debug!(target: "app", "netsh proxy output: {}", stdout);
unsafe {
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
.encode_utf16()
.collect::<Vec<u16>>();
let mut proxy_enabled = false;
let mut proxy_server = String::new();
let mut bypass_list = String::new();
let mut hkey: HKEY = ptr::null_mut();
let result =
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
for line in stdout.lines() {
let line = line.trim();
if line.starts_with("代理服务器") || line.starts_with("Proxy Server") {
if let Some(server_part) = line.split(':').nth(1) {
let server = server_part.trim();
if !server.is_empty() && server != "(none)" && server != "" {
proxy_server = server.to_string();
proxy_enabled = true;
}
}
} else if line.starts_with("绕过列表") || line.starts_with("Bypass List") {
if let Some(bypass_part) = line.split(':').nth(1) {
bypass_list = bypass_part.trim().to_string();
}
if result != 0 {
return Ok(AsyncSysproxy::default());
}
}
if proxy_enabled && !proxy_server.is_empty() {
// 解析服务器地址和端口
let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') {
let host = proxy_server[..colon_pos].to_string();
let port = proxy_server[colon_pos + 1..].parse::<u16>().unwrap_or(8080);
(host, port)
// 检查代理是否启用
let proxy_enable_name = "ProxyEnable\0".encode_utf16().collect::<Vec<u16>>();
let mut proxy_enable: DWORD = 0;
let mut buffer_size: DWORD = 4;
let mut value_type: DWORD = 0;
let enable_result = RegQueryValueExW(
hkey,
proxy_enable_name.as_ptr(),
ptr::null_mut(),
&mut value_type,
&mut proxy_enable as *mut DWORD as *mut u8,
&mut buffer_size,
);
if enable_result != 0 || value_type != REG_DWORD || proxy_enable == 0 {
RegCloseKey(hkey);
return Ok(AsyncSysproxy::default());
}
// 读取代理服务器设置
let proxy_server_name = "ProxyServer\0".encode_utf16().collect::<Vec<u16>>();
let mut buffer = vec![0u16; 1024];
let mut buffer_size: DWORD = (buffer.len() * 2) as DWORD;
let mut value_type: DWORD = 0;
let server_result = RegQueryValueExW(
hkey,
proxy_server_name.as_ptr(),
ptr::null_mut(),
&mut value_type,
buffer.as_mut_ptr() as *mut u8,
&mut buffer_size,
);
let mut proxy_server = String::new();
if server_result == 0 && value_type == REG_SZ && buffer_size > 0 {
let end_pos = buffer.iter().position(|&x| x == 0).unwrap_or(buffer.len());
proxy_server = String::from_utf16_lossy(&buffer[..end_pos]);
}
// 读取代理绕过列表
let proxy_override_name = "ProxyOverride\0".encode_utf16().collect::<Vec<u16>>();
let mut bypass_buffer = vec![0u16; 1024];
let mut bypass_buffer_size: DWORD = (bypass_buffer.len() * 2) as DWORD;
let mut bypass_value_type: DWORD = 0;
let override_result = RegQueryValueExW(
hkey,
proxy_override_name.as_ptr(),
ptr::null_mut(),
&mut bypass_value_type,
bypass_buffer.as_mut_ptr() as *mut u8,
&mut bypass_buffer_size,
);
let mut bypass_list = String::new();
if override_result == 0 && bypass_value_type == REG_SZ && bypass_buffer_size > 0 {
let end_pos = bypass_buffer
.iter()
.position(|&x| x == 0)
.unwrap_or(bypass_buffer.len());
bypass_list = String::from_utf16_lossy(&bypass_buffer[..end_pos]);
}
RegCloseKey(hkey);
if !proxy_server.is_empty() {
// 解析服务器地址和端口
let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') {
let host = proxy_server[..colon_pos].to_string();
let port = proxy_server[colon_pos + 1..].parse::<u16>().unwrap_or(8080);
(host, port)
} else {
(proxy_server, 8080)
};
log::debug!(target: "app", "从注册表读取到代理设置: {}:{}, bypass: {}", host, port, bypass_list);
Ok(AsyncSysproxy {
enable: true,
host,
port,
bypass: bypass_list,
})
} else {
(proxy_server, 8080)
};
Ok(AsyncSysproxy {
enable: true,
host,
port,
bypass: bypass_list,
})
} else {
Ok(AsyncSysproxy::default())
Ok(AsyncSysproxy::default())
}
}
}
+137 -88
View File
@@ -1,5 +1,3 @@
#[cfg(target_os = "macos")]
use crate::core::tray::Tray;
use crate::{
config::*,
core::{
@@ -516,70 +514,102 @@ impl CoreManager {
Ok(())
}
/// 根据进程名查找进程PID列
/// 根据进程名查找进程PID列
async fn find_processes_by_name(
&self,
process_name: String,
_target: &str,
) -> Result<(Vec<u32>, String)> {
let output = if cfg!(windows) {
tokio::process::Command::new("tasklist")
.args([
"/FI",
&format!("IMAGENAME eq {}", process_name),
"/FO",
"CSV",
"/NH",
])
.output()
.await?
} else if cfg!(target_os = "macos") {
tokio::process::Command::new("pgrep")
.arg(&process_name)
.output()
.await?
} else {
// Linux
tokio::process::Command::new("pidof")
.arg(&process_name)
.output()
.await?
};
#[cfg(windows)]
{
use std::mem;
use winapi::um::handleapi::CloseHandle;
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
TH32CS_SNAPPROCESS,
};
use winapi::um::winnt::HANDLE;
if !output.status.success() {
return Ok((Vec::new(), process_name));
}
let process_name_clone = process_name.clone();
let pids = tokio::task::spawn_blocking(move || -> Result<Vec<u32>> {
let mut pids = Vec::new();
let stdout = String::from_utf8_lossy(&output.stdout);
let mut pids = Vec::new();
unsafe {
// 创建进程快照
let snapshot: HANDLE = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if snapshot == winapi::um::handleapi::INVALID_HANDLE_VALUE {
return Err(anyhow::anyhow!("Failed to create process snapshot"));
}
if cfg!(windows) {
// 解析CSV格式输出: "进程名","PID","会话名","会话#","内存使用"
for line in stdout.lines() {
if !line.is_empty() && line.contains(&process_name) {
let fields: Vec<&str> = line.split(',').collect();
if fields.len() >= 2 {
// 移除引号并解析PID
let pid_str = fields[1].trim_matches('"');
if let Ok(pid) = pid_str.parse::<u32>() {
pids.push(pid);
let mut pe32: PROCESSENTRY32W = mem::zeroed();
pe32.dwSize = mem::size_of::<PROCESSENTRY32W>() as u32;
// 获取第一个进程
if Process32FirstW(snapshot, &mut pe32) != 0 {
loop {
// 将宽字符转换为String
let end_pos = pe32
.szExeFile
.iter()
.position(|&x| x == 0)
.unwrap_or(pe32.szExeFile.len());
let exe_file = String::from_utf16_lossy(&pe32.szExeFile[..end_pos]);
// 检查进程名是否匹配
if exe_file.eq_ignore_ascii_case(&process_name_clone) {
pids.push(pe32.th32ProcessID);
}
if Process32NextW(snapshot, &mut pe32) == 0 {
break;
}
}
}
// 关闭句柄
CloseHandle(snapshot);
}
Ok(pids)
})
.await??;
Ok((pids, process_name))
}
#[cfg(not(windows))]
{
let output = if cfg!(target_os = "macos") {
tokio::process::Command::new("pgrep")
.arg(&process_name)
.output()
.await?
} else {
// Linux
tokio::process::Command::new("pidof")
.arg(&process_name)
.output()
.await?
};
if !output.status.success() {
return Ok((Vec::new(), process_name));
}
} else {
let stdout = String::from_utf8_lossy(&output.stdout);
let mut pids = Vec::new();
// Unix系统直接解析PID列表
for pid_str in stdout.split_whitespace() {
if let Ok(pid) = pid_str.parse::<u32>() {
pids.push(pid);
}
}
}
Ok((pids, process_name))
Ok((pids, process_name))
}
}
/// 终止进程并验证结果
/// 终止进程并验证结果 - 使用Windows API直接终止,更优雅高效
async fn kill_process_with_verification(&self, pid: u32, process_name: String) -> bool {
logging!(
info,
@@ -590,14 +620,30 @@ impl CoreManager {
pid
);
let success = if cfg!(windows) {
tokio::process::Command::new("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.output()
.await
.map(|output| output.status.success())
.unwrap_or(false)
} else {
#[cfg(windows)]
let success = {
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{OpenProcess, TerminateProcess};
use winapi::um::winnt::{HANDLE, PROCESS_TERMINATE};
tokio::task::spawn_blocking(move || -> bool {
unsafe {
let process_handle: HANDLE = OpenProcess(PROCESS_TERMINATE, 0, pid);
if process_handle.is_null() {
return false;
}
let result = TerminateProcess(process_handle, 1);
CloseHandle(process_handle);
result != 0
}
})
.await
.unwrap_or(false)
};
#[cfg(not(windows))]
let success = {
tokio::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.output()
@@ -645,34 +691,49 @@ impl CoreManager {
}
}
/// 检查进程是否仍在运行
/// Windows API检查进程
async fn is_process_running(&self, pid: u32) -> Result<bool> {
let output = if cfg!(windows) {
tokio::process::Command::new("tasklist")
.args(["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"])
.output()
.await?
} else {
tokio::process::Command::new("ps")
#[cfg(windows)]
{
use winapi::shared::minwindef::DWORD;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::GetExitCodeProcess;
use winapi::um::processthreadsapi::OpenProcess;
use winapi::um::winnt::{HANDLE, PROCESS_QUERY_INFORMATION};
let result = tokio::task::spawn_blocking(move || -> Result<bool> {
unsafe {
let process_handle: HANDLE = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
if process_handle.is_null() {
return Ok(false);
}
let mut exit_code: DWORD = 0;
let result = GetExitCodeProcess(process_handle, &mut exit_code);
CloseHandle(process_handle);
if result == 0 {
return Ok(false);
}
Ok(exit_code == 259)
}
})
.await?;
result
}
#[cfg(not(windows))]
{
let output = tokio::process::Command::new("ps")
.args(["-p", &pid.to_string()])
.output()
.await?
};
.await?;
Ok(output.status.success() && !output.stdout.is_empty())
Ok(output.status.success() && !output.stdout.is_empty())
}
}
async fn start_core_by_sidecar(&self) -> Result<()> {
if let Err(e) = self.cleanup_orphaned_mihomo_processes().await {
logging!(
warn,
Type::Core,
true,
"清理多余 mihomo 进程时发生错误: {}",
e
);
}
logging!(trace, Type::Core, true, "Running core by sidecar");
let config_file = &Config::generate_file(ConfigType::Run)?;
let app_handle = handle::Handle::global()
@@ -980,9 +1041,8 @@ impl CoreManager {
}
logging!(trace, Type::Core, "Initied core logic completed");
#[cfg(target_os = "macos")]
logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await);
// #[cfg(target_os = "macos")]
// logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await);
Ok(())
}
@@ -1036,17 +1096,6 @@ impl CoreManager {
pub async fn restart_core(&self) -> Result<()> {
self.stop_core().await?;
// 在重启时也清理多余的 mihomo 进程
if let Err(e) = self.cleanup_orphaned_mihomo_processes().await {
logging!(
warn,
Type::Core,
true,
"重启时清理多余 mihomo 进程失败: {}",
e
);
}
self.start_core().await?;
Ok(())
}
@@ -526,16 +526,10 @@ impl EventDrivenProxyManager {
#[cfg(target_os = "windows")]
async fn execute_sysproxy_command(args: &[&str]) {
use crate::{core::handle::Handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = match Handle::global().app_handle() {
Some(handle) => handle,
None => {
log::error!(target: "app", "获取应用句柄失败");
return;
}
};
use crate::utils::dirs;
#[allow(unused_imports)] // creation_flags必须
use std::os::windows::process::CommandExt;
use tokio::process::Command;
let binary_path = match dirs::service_path() {
Ok(path) => path,
@@ -551,10 +545,9 @@ impl EventDrivenProxyManager {
return;
}
let shell = app_handle.shell();
let output = shell
.command(sysproxy_exe.as_path().to_str().unwrap())
let output = Command::new(sysproxy_exe)
.args(args)
.creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口
.output()
.await;
@@ -562,6 +555,12 @@ impl EventDrivenProxyManager {
Ok(output) => {
if !output.status.success() {
log::error!(target: "app", "执行sysproxy命令失败: {:?}", args);
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.is_empty() {
log::error!(target: "app", "sysproxy错误输出: {}", stderr);
}
} else {
log::debug!(target: "app", "成功执行sysproxy命令: {:?}", args);
}
}
Err(e) => {
+10 -80
View File
@@ -1,10 +1,6 @@
use crate::{
config::Config,
core::handle,
feat, logging, logging_error,
module::lightweight::entry_lightweight_mode,
process::AsyncHandler,
utils::{logging::Type, resolve},
config::Config, core::handle, feat, logging, logging_error,
module::lightweight::entry_lightweight_mode, utils::logging::Type,
};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
@@ -14,7 +10,7 @@ use tauri::Manager;
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
current: Arc<Mutex<Vec<String>>>,
}
impl Hotkey {
@@ -38,7 +34,6 @@ impl Hotkey {
enable_global_hotkey
);
// 如果全局热键被禁用,则不注册热键
if !enable_global_hotkey {
return Ok(());
}
@@ -153,76 +148,14 @@ impl Hotkey {
"=== Hotkey Dashboard Window Operation Start ==="
);
// 检查是否在轻量模式下,如果是,需要同步处理
if crate::module::lightweight::is_in_lightweight_mode() {
logging!(
info,
Type::Hotkey,
true,
"In lightweight mode, calling open_or_close_dashboard directly"
);
crate::feat::open_or_close_dashboard();
} else {
AsyncHandler::spawn(move || async move {
logging!(
debug,
Type::Hotkey,
true,
"Toggle dashboard window visibility (async)"
);
logging!(
info,
Type::Hotkey,
true,
"Using unified WindowManager for hotkey operation (bypass debounce)"
);
// 检查窗口是否存在
if let Some(window) = handle::Handle::global().get_window() {
// 如果窗口可见,则隐藏
match window.is_visible() {
Ok(visible) => {
if visible {
logging!(
info,
Type::Window,
true,
"Window is visible, hiding it"
);
let _ = window.hide();
} else {
// 如果窗口不可见,则显示
logging!(
info,
Type::Window,
true,
"Window is hidden, showing it"
);
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
let _ = window.show();
let _ = window.set_focus();
}
}
Err(e) => {
logging!(
warn,
Type::Window,
true,
"Failed to check window visibility: {}",
e
);
let _ = window.show();
let _ = window.set_focus();
}
}
} else {
// 如果窗口不存在,创建一个新窗口
logging!(
info,
Type::Window,
true,
"Window does not exist, creating a new one"
);
resolve::create_window(true);
}
});
}
crate::feat::open_or_close_dashboard_hotkey();
logging!(
debug,
@@ -261,10 +194,8 @@ impl Hotkey {
}
}
} else {
// 直接执行函数,不做任何状态检查
logging!(debug, Type::Hotkey, "Executing function directly");
// 获取全局热键状态
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
@@ -274,7 +205,6 @@ impl Hotkey {
f();
} else {
use crate::utils::window_manager::WindowManager;
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
let is_visible = WindowManager::is_main_window_visible();
let is_focused = WindowManager::is_main_window_focused();
+44 -218
View File
@@ -12,15 +12,7 @@ use crate::{
};
use anyhow::Result;
#[cfg(target_os = "macos")]
use futures::StreamExt;
use parking_lot::Mutex;
#[cfg(target_os = "macos")]
use parking_lot::RwLock;
#[cfg(target_os = "macos")]
pub use speed_rate::{SpeedRate, Traffic};
#[cfg(target_os = "macos")]
use std::sync::Arc;
use std::{
fs,
sync::atomic::{AtomicBool, Ordering},
@@ -31,20 +23,37 @@ use tauri::{
tray::{MouseButton, MouseButtonState, TrayIconEvent},
AppHandle, Wry,
};
#[cfg(target_os = "macos")]
use tokio::sync::broadcast;
use super::handle;
#[derive(Clone)]
struct TrayState {}
// 托盘点击防抖机制
static TRAY_CLICK_DEBOUNCE: OnceCell<Mutex<Instant>> = OnceCell::new();
const TRAY_CLICK_DEBOUNCE_MS: u64 = 300;
fn get_tray_click_debounce() -> &'static Mutex<Instant> {
TRAY_CLICK_DEBOUNCE.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(1)))
}
fn should_handle_tray_click() -> bool {
let debounce_lock = get_tray_click_debounce();
let mut last_click = debounce_lock.lock();
let now = Instant::now();
if now.duration_since(*last_click) >= Duration::from_millis(TRAY_CLICK_DEBOUNCE_MS) {
*last_click = now;
true
} else {
log::debug!(target: "app", "托盘点击被防抖机制忽略,距离上次点击 {:?}ms",
now.duration_since(*last_click).as_millis());
false
}
}
#[cfg(target_os = "macos")]
pub struct Tray {
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
is_subscribed: Arc<RwLock<bool>>,
pub rate_cache: Arc<Mutex<Option<Rate>>>,
last_menu_update: Mutex<Option<Instant>>,
menu_updating: AtomicBool,
}
@@ -105,7 +114,7 @@ impl TrayState {
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
include_bytes!("../../../icons/tray-icon-sys-mono-new.ico").to_vec(),
)
} else {
(
@@ -139,7 +148,7 @@ impl TrayState {
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
include_bytes!("../../../icons/tray-icon-tun-mono-new.ico").to_vec(),
)
} else {
(
@@ -164,10 +173,6 @@ impl Tray {
#[cfg(target_os = "macos")]
return TRAY.get_or_init(|| Tray {
speed_rate: Arc::new(Mutex::new(None)),
shutdown_tx: Arc::new(RwLock::new(None)),
is_subscribed: Arc::new(RwLock::new(false)),
rate_cache: Arc::new(Mutex::new(None)),
last_menu_update: Mutex::new(None),
menu_updating: AtomicBool::new(false),
});
@@ -180,11 +185,6 @@ impl Tray {
}
pub fn init(&self) -> Result<()> {
#[cfg(target_os = "macos")]
{
let mut speed_rate = self.speed_rate.lock();
*speed_rate = Some(SpeedRate::new());
}
Ok(())
}
@@ -291,7 +291,7 @@ impl Tray {
/// 更新托盘图标
#[cfg(target_os = "macos")]
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
pub fn update_icon(&self, _rate: Option<Rate>) -> Result<()> {
let app_handle = match handle::Handle::global().app_handle() {
Some(handle) => handle,
None => {
@@ -312,55 +312,18 @@ impl Tray {
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
let (_is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
(true, true) => TrayState::get_tun_tray_icon(),
(true, false) => TrayState::get_sysproxy_tray_icon(),
(false, true) => TrayState::get_tun_tray_icon(),
(false, false) => TrayState::get_common_tray_icon(),
};
let enable_tray_speed = verge.enable_tray_speed.unwrap_or(false);
let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true);
let colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
let is_colorful = colorful == "colorful";
if !enable_tray_speed {
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
let _ = tray.set_icon_as_template(!is_colorful);
return Ok(());
}
let rate = if let Some(rate) = rate {
Some(rate)
} else {
let guard = self.speed_rate.lock();
if let Some(guard) = guard.as_ref() {
if let Some(rate) = guard.get_curent_rate() {
Some(rate)
} else {
Some(Rate::default())
}
} else {
Some(Rate::default())
}
};
let mut rate_guard = self.rate_cache.lock();
if *rate_guard != rate {
*rate_guard = rate;
let bytes = if enable_tray_icon {
Some(icon_bytes)
} else {
None
};
let rate = rate_guard.as_ref();
if let Ok(rate_bytes) = SpeedRate::add_speed_text(is_custom_icon, bytes, rate) {
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
}
}
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
let _ = tray.set_icon_as_template(!is_colorful);
Ok(())
}
@@ -475,155 +438,9 @@ impl Tray {
Ok(())
}
/// 订阅流量数据
#[cfg(target_os = "macos")]
pub async fn subscribe_traffic(&self) -> Result<()> {
log::info!(target: "app", "subscribe traffic");
// 如果已经订阅,先取消订阅
if *self.is_subscribed.read() {
self.unsubscribe_traffic();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
let (shutdown_tx, shutdown_rx) = broadcast::channel(3);
*self.shutdown_tx.write() = Some(shutdown_tx);
*self.is_subscribed.write() = true;
let speed_rate = Arc::clone(&self.speed_rate);
let is_subscribed = Arc::clone(&self.is_subscribed);
// 使用单线程防止阻塞主线程
std::thread::Builder::new()
.name("traffic-monitor".into())
.spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to build tokio runtime for traffic monitor");
// 在单独的运行时中执行异步任务
rt.block_on(async move {
let mut shutdown = shutdown_rx;
let speed_rate = speed_rate.clone();
let is_subscribed = is_subscribed.clone();
let mut consecutive_errors = 0;
let max_consecutive_errors = 5;
let mut interval = tokio::time::interval(std::time::Duration::from_secs(10));
'outer: loop {
if !*is_subscribed.read() {
log::info!(target: "app", "Traffic subscription has been cancelled");
break;
}
match tokio::time::timeout(
std::time::Duration::from_secs(5),
Traffic::get_traffic_stream()
).await {
Ok(stream_result) => {
match stream_result {
Ok(mut stream) => {
consecutive_errors = 0;
loop {
tokio::select! {
traffic_result = stream.next() => {
match traffic_result {
Some(Ok(traffic)) => {
if let Ok(Some(rate)) = tokio::time::timeout(
std::time::Duration::from_millis(50),
async {
let guard = speed_rate.try_lock();
if let Some(guard) = guard {
if let Some(sr) = guard.as_ref() {
sr.update_and_check_changed(traffic.up, traffic.down)
} else {
None
}
} else {
None
}
}
).await {
let _ = tokio::time::timeout(
std::time::Duration::from_millis(100),
async { let _ = Tray::global().update_icon(Some(rate)); }
).await;
}
},
Some(Err(e)) => {
log::error!(target: "app", "Traffic stream error: {}", e);
consecutive_errors += 1;
if consecutive_errors >= max_consecutive_errors {
log::error!(target: "app", "Too many errors, reconnecting traffic stream");
break;
}
},
None => {
log::info!(target: "app", "Traffic stream ended, reconnecting");
break;
}
}
},
_ = shutdown.recv() => {
log::info!(target: "app", "Received shutdown signal for traffic stream");
break 'outer;
},
_ = interval.tick() => {
if !*is_subscribed.read() {
log::info!(target: "app", "Traffic monitor detected subscription cancelled");
break 'outer;
}
log::debug!(target: "app", "Traffic subscription periodic health check");
},
_ = tokio::time::sleep(std::time::Duration::from_secs(60)) => {
log::info!(target: "app", "Traffic stream max active time reached, reconnecting");
break;
}
}
}
},
Err(e) => {
log::error!(target: "app", "Failed to get traffic stream: {}", e);
consecutive_errors += 1;
if consecutive_errors >= max_consecutive_errors {
log::error!(target: "app", "Too many consecutive errors, pausing traffic monitoring");
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
consecutive_errors = 0;
} else {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}
}
}
},
Err(_) => {
log::error!(target: "app", "Traffic stream initialization timed out");
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}
}
if !*is_subscribed.read() {
break;
}
}
log::info!(target: "app", "Traffic subscription thread terminated");
});
})
.expect("Failed to spawn traffic monitor thread");
Ok(())
}
/// 取消订阅 traffic 数据
#[cfg(target_os = "macos")]
pub fn unsubscribe_traffic(&self) {
log::info!(target: "app", "unsubscribe traffic");
*self.is_subscribed.write() = false;
if let Some(tx) = self.shutdown_tx.write().take() {
drop(tx);
}
}
pub fn unsubscribe_traffic(&self) {}
pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> {
log::info!(target: "app", "正在从AppHandle创建系统托盘");
@@ -664,6 +481,11 @@ impl Tray {
..
} = event
{
// 添加防抖检查,防止快速连击
if !should_handle_tray_click() {
return;
}
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
@@ -949,12 +771,15 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
"open_window" => {
use crate::utils::window_manager::WindowManager;
log::info!(target: "app", "托盘菜单点击: 打开窗口");
// 如果在轻量模式中,先退出轻量模式
if !should_handle_tray_click() {
return;
}
if crate::module::lightweight::is_in_lightweight_mode() {
log::info!(target: "app", "当前在轻量模式,正在退出");
crate::module::lightweight::exit_lightweight_mode();
}
// 使用统一的窗口管理器显示窗口
let result = WindowManager::show_main_window();
log::info!(target: "app", "窗口显示结果: {:?}", result);
}
@@ -977,7 +802,10 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
"restart_clash" => feat::restart_clash_core(),
"restart_app" => feat::restart_app(),
"entry_lightweight_mode" => {
// 处理轻量模式的切换
if !should_handle_tray_click() {
return;
}
let was_lightweight = crate::module::lightweight::is_in_lightweight_mode();
if was_lightweight {
crate::module::lightweight::exit_lightweight_mode();
@@ -985,7 +813,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
crate::module::lightweight::entry_lightweight_mode();
}
// 退出轻量模式后显示主窗口
if was_lightweight {
use crate::utils::window_manager::WindowManager;
let result = WindowManager::show_main_window();
@@ -1002,7 +829,6 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
_ => {}
}
// 统一调用状态更新
if let Err(e) = Tray::global().update_all_states() {
log::warn!(target: "app", "更新托盘状态失败: {}", e);
}
@@ -1,336 +1 @@
use crate::{
module::mihomo::{MihomoManager, Rate},
utils::help::format_bytes_speed,
};
use ab_glyph::FontArc;
use anyhow::Result;
use futures::Stream;
use image::{GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut;
use parking_lot::Mutex;
use std::{io::Cursor, sync::Arc};
use tokio_tungstenite::tungstenite::http;
use tungstenite::client::IntoClientRequest;
#[derive(Debug, Clone)]
pub struct SpeedRate {
rate: Arc<Mutex<(Rate, Rate)>>,
last_update: Arc<Mutex<std::time::Instant>>,
}
impl SpeedRate {
pub fn new() -> Self {
Self {
rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))),
last_update: Arc::new(Mutex::new(std::time::Instant::now())),
}
}
pub fn update_and_check_changed(&self, up: u64, down: u64) -> Option<Rate> {
let mut rates = self.rate.lock();
let mut last_update = self.last_update.lock();
let now = std::time::Instant::now();
// 限制更新频率为每秒最多2次(500ms)
if now.duration_since(*last_update).as_millis() < 500 {
return None;
}
let (current, previous) = &mut *rates;
// Avoid unnecessary float conversions for small value checks
let should_update = if current.up < 1000 && down < 1000 {
// For small values, always update to ensure accuracy
current.up != up || current.down != down
} else {
// For larger values, use integer math to check for >5% change
// Multiply by 20 instead of dividing by 0.05 to avoid floating point
let up_threshold = current.up / 20;
let down_threshold = current.down / 20;
(up > current.up && up - current.up > up_threshold)
|| (up < current.up && current.up - up > up_threshold)
|| (down > current.down && down - current.down > down_threshold)
|| (down < current.down && current.down - down > down_threshold)
};
if !should_update {
return None;
}
*previous = current.clone();
current.up = up;
current.down = down;
*last_update = now;
if previous != current {
Some(current.clone())
} else {
None
}
}
pub fn get_curent_rate(&self) -> Option<Rate> {
let rates = self.rate.lock();
let (current, _) = &*rates;
Some(current.clone())
}
// 分离图标加载和速率渲染
pub fn add_speed_text(
is_custom_icon: bool,
icon_bytes: Option<Vec<u8>>,
rate: Option<&Rate>,
) -> Result<Vec<u8>> {
let rate = rate.unwrap_or(&Rate { up: 0, down: 0 });
let (mut icon_width, mut icon_height) = (0, 256);
let icon_image = if let Some(bytes) = icon_bytes.clone() {
let icon_image = image::load_from_memory(&bytes)?;
icon_width = icon_image.width();
icon_height = icon_image.height();
icon_image
} else {
// 返回一个空的 RGBA 图像
image::DynamicImage::new_rgba8(0, 0)
};
let total_width = match (is_custom_icon, icon_bytes.is_some()) {
(true, true) => 510,
(true, false) => 740,
(false, false) => 740,
(false, true) => icon_width + 740,
};
// println!(
// "icon_height: {}, icon_wight: {}, total_width: {}",
// icon_height, icon_width, total_width
// );
// 创建新的透明画布
let mut combined_image = RgbaImage::new(total_width, icon_height);
// 将原始图标绘制到新画布的左侧
if icon_bytes.is_some() {
for y in 0..icon_height {
for x in 0..icon_width {
let pixel = icon_image.get_pixel(x, y);
combined_image.put_pixel(x, y, pixel);
}
}
}
let is_colorful = if let Some(bytes) = icon_bytes.clone() {
!crate::utils::help::is_monochrome_image_from_bytes(&bytes).unwrap_or(false)
} else {
false
};
// 选择文本颜色
let (text_color, shadow_color) = if is_colorful {
(
Rgba([144u8, 144u8, 144u8, 255u8]),
// Rgba([255u8, 255u8, 255u8, 128u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
)
// (
// Rgba([160u8, 160u8, 160u8, 255u8]),
// // Rgba([255u8, 255u8, 255u8, 128u8]),
// Rgba([0u8, 0u8, 0u8, 255u8]),
// )
} else {
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
)
};
// 减小字体大小以适应文本区域
let font_data = include_bytes!("../../../assets/fonts/SF-Pro.ttf");
let font = FontArc::try_from_vec(font_data.to_vec()).unwrap();
let font_size = icon_height as f32 * 0.6; // 稍微减小字体
let scale = ab_glyph::PxScale::from(font_size);
// 使用更简洁的速率格式
let up_text = format!("{}", format_bytes_speed(rate.up));
let down_text = format!("{}", format_bytes_speed(rate.down));
// For test rate display
// let down_text = format!("↓ {}", format_bytes_speed(102 * 1020 * 1024));
// 计算文本位置,确保垂直间距合适
// 修改文本位置为居右显示
// 计算右对齐的文本位置
// let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
// let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
// let up_text_x = total_width - up_text_width;
// let down_text_x = total_width - down_text_width;
// 计算左对齐的文本位置
let (up_text_x, down_text_x) = {
if is_custom_icon || icon_bytes.is_some() {
let text_left_offset = 30;
let left_begin = icon_width + text_left_offset;
(left_begin, left_begin)
} else {
(icon_width, icon_width)
}
};
// 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小
let text_height = font_size as i32;
let total_text_height = text_height * 2;
let up_y = (icon_height as i32 - total_text_height) / 2;
let down_y = up_y + text_height;
// 绘制速率文本(先阴影后文字)
let shadow_offset = 1;
// 绘制上行速率
draw_text_mut(
&mut combined_image,
shadow_color,
up_text_x as i32 + shadow_offset,
up_y + shadow_offset,
scale,
&font,
&up_text,
);
draw_text_mut(
&mut combined_image,
text_color,
up_text_x as i32,
up_y,
scale,
&font,
&up_text,
);
// 绘制下行速率
draw_text_mut(
&mut combined_image,
shadow_color,
down_text_x as i32 + shadow_offset,
down_y + shadow_offset,
scale,
&font,
&down_text,
);
draw_text_mut(
&mut combined_image,
text_color,
down_text_x as i32,
down_y,
scale,
&font,
&down_text,
);
// 将结果转换为 PNG 数据
let mut bytes = Vec::new();
combined_image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?;
Ok(bytes)
}
}
#[derive(Debug, Clone)]
pub struct Traffic {
pub up: u64,
pub down: u64,
}
impl Traffic {
pub async fn get_traffic_stream() -> Result<impl Stream<Item = Result<Traffic, anyhow::Error>>>
{
use futures::{
future::FutureExt,
stream::{self, StreamExt},
};
use std::time::Duration;
// 先处理错误和超时情况
let stream = Box::pin(
stream::unfold((), move |_| async move {
'retry: loop {
log::info!(target: "app", "establishing traffic websocket connection");
let (url, token) = MihomoManager::get_traffic_ws_url();
let mut request = match url.into_client_request() {
Ok(req) => req,
Err(e) => {
log::error!(target: "app", "failed to create websocket request: {}", e);
tokio::time::sleep(Duration::from_secs(2)).await;
continue 'retry;
}
};
request.headers_mut().insert(http::header::AUTHORIZATION, token);
match tokio::time::timeout(Duration::from_secs(3),
tokio_tungstenite::connect_async(request)
).await {
Ok(Ok((ws_stream, _))) => {
log::info!(target: "app", "traffic websocket connection established");
// 设置流超时控制
let traffic_stream = ws_stream
.take_while(|msg| {
let continue_stream = msg.is_ok();
async move { continue_stream }.boxed()
})
.filter_map(|msg| async move {
match msg {
Ok(msg) => {
if !msg.is_text() {
return None;
}
match tokio::time::timeout(
Duration::from_millis(200),
async { msg.into_text() }
).await {
Ok(Ok(text)) => {
match serde_json::from_str::<serde_json::Value>(&text) {
Ok(json) => {
let up = json["up"].as_u64().unwrap_or(0);
let down = json["down"].as_u64().unwrap_or(0);
Some(Ok(Traffic { up, down }))
},
Err(e) => {
log::warn!(target: "app", "traffic json parse error: {} for {}", e, text);
None
}
}
},
Ok(Err(e)) => {
log::warn!(target: "app", "traffic text conversion error: {}", e);
None
},
Err(_) => {
log::warn!(target: "app", "traffic text processing timeout");
None
}
}
},
Err(e) => {
log::error!(target: "app", "traffic websocket error: {}", e);
None
}
}
});
return Some((traffic_stream, ()));
},
Ok(Err(e)) => {
log::error!(target: "app", "traffic websocket connection failed: {}", e);
},
Err(_) => {
log::error!(target: "app", "traffic websocket connection timed out");
}
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
})
.flatten(),
);
Ok(stream)
}
}
+34 -3
View File
@@ -11,11 +11,43 @@ use crate::{
/// Open or close the dashboard window
#[allow(dead_code)]
pub fn open_or_close_dashboard() {
open_or_close_dashboard_internal(false)
}
/// Open or close the dashboard window (hotkey call, dispatched to main thread)
#[allow(dead_code)]
pub fn open_or_close_dashboard_hotkey() {
open_or_close_dashboard_internal(true)
}
/// Internal implementation for opening/closing dashboard
fn open_or_close_dashboard_internal(bypass_debounce: bool) {
use crate::process::AsyncHandler;
use crate::utils::window_manager::WindowManager;
log::info!(target: "app", "Attempting to open/close dashboard");
log::info!(target: "app", "Attempting to open/close dashboard (绕过防抖: {})", bypass_debounce);
// 检查是否在轻量模式下
// 热键调用调度到主线程执行,避免 WebView 创建死锁
if bypass_debounce {
log::info!(target: "app", "热键调用,调度到主线程执行窗口操作");
AsyncHandler::spawn(move || async move {
log::info!(target: "app", "主线程中执行热键窗口操作");
if crate::module::lightweight::is_in_lightweight_mode() {
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
crate::module::lightweight::exit_lightweight_mode();
log::info!(target: "app", "Creating new window after exiting lightweight mode");
let result = WindowManager::show_main_window();
log::info!(target: "app", "Window operation result: {:?}", result);
return;
}
let result = WindowManager::toggle_main_window();
log::info!(target: "app", "Window toggle result: {:?}", result);
});
return;
}
if crate::module::lightweight::is_in_lightweight_mode() {
log::info!(target: "app", "Currently in lightweight mode, exiting lightweight mode");
crate::module::lightweight::exit_lightweight_mode();
@@ -25,7 +57,6 @@ pub fn open_or_close_dashboard() {
return;
}
// 使用统一的窗口管理器切换窗口状态
let result = WindowManager::toggle_main_window();
log::info!(target: "app", "Window toggle result: {:?}", result);
}
@@ -13,11 +13,17 @@ use crate::AppHandleManager;
use anyhow::{Context, Result};
use delay_timer::prelude::TaskBuilder;
use std::sync::Mutex;
use std::sync::{
atomic::{AtomicBool, Ordering},
Mutex,
};
use tauri::{Listener, Manager};
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
// 添加退出轻量模式的锁,防止并发调用
static EXITING_LIGHTWEIGHT: AtomicBool = AtomicBool::new(false);
fn with_lightweight_status<F, R>(f: F) -> R
where
F: FnOnce(&mut LightWeightState) -> R,
@@ -131,6 +137,25 @@ pub fn entry_lightweight_mode() {
// 添加从轻量模式恢复的函数
pub fn exit_lightweight_mode() {
// 使用原子操作检查是否已经在退出过程中,防止并发调用
if EXITING_LIGHTWEIGHT
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
logging!(
info,
Type::Lightweight,
true,
"轻量模式退出操作已在进行中,跳过重复调用"
);
return;
}
// 使用defer确保无论如何都会重置标志
let _guard = scopeguard::guard((), |_| {
EXITING_LIGHTWEIGHT.store(false, Ordering::SeqCst);
});
// 确保当前确实处于轻量模式才执行退出操作
if !is_in_lightweight_mode() {
logging!(info, Type::Lightweight, true, "当前不在轻量模式,无需退出");
+1 -29
View File
@@ -4,8 +4,6 @@ use once_cell::sync::Lazy;
use parking_lot::{Mutex, RwLock};
use std::time::{Duration, Instant};
use tauri::http::HeaderMap;
#[cfg(target_os = "macos")]
use tauri::http::HeaderValue;
// 缓存的最大有效期(5秒)
const CACHE_TTL: Duration = Duration::from_secs(5);
@@ -106,31 +104,5 @@ impl MihomoManager {
Some((server, headers))
}
// 提供默认值的版本,避免在connection_info为None时panic
#[cfg(target_os = "macos")]
fn get_clash_client_info_or_default() -> (String, HeaderMap) {
Self::get_clash_client_info().unwrap_or_else(|| {
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
("http://127.0.0.1:9090".to_string(), headers)
})
}
#[cfg(target_os = "macos")]
pub fn get_traffic_ws_url() -> (String, HeaderValue) {
let (url, headers) = MihomoManager::get_clash_client_info_or_default();
let ws_url = url.replace("http://", "ws://") + "/traffic";
let auth = headers
.get("Authorization")
.map(|val| val.to_str().unwrap_or("").to_string())
.unwrap_or_default();
// 创建默认的空HeaderValue而不是使用unwrap_or_default
let token = match HeaderValue::from_str(&auth) {
Ok(v) => v,
Err(_) => HeaderValue::from_static(""),
};
(ws_url, token)
}
// 已移除未使用的 get_clash_client_info_or_default 和 get_traffic_ws_url 方法
}
@@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
use log::info;
#[cfg(target_os = "windows")]
use std::{fs, path::Path, path::PathBuf};
use std::{fs, os::windows::process::CommandExt, path::Path, path::PathBuf};
/// Windows 下的开机启动文件夹路径
#[cfg(target_os = "windows")]
@@ -59,6 +59,8 @@ pub fn create_shortcut() -> Result<()> {
let output = std::process::Command::new("powershell")
.args(["-Command", &powershell_command])
// 隐藏 PowerShell 窗口
.creation_flags(0x08000000) // CREATE_NO_WINDOW
.output()
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;
@@ -125,19 +125,6 @@ pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> {
Ok(())
}
#[cfg(target_os = "macos")]
pub fn is_monochrome_image_from_bytes(data: &[u8]) -> anyhow::Result<bool> {
let img = image::load_from_memory(data)?;
let rgb_img = img.to_rgb8();
for pixel in rgb_img.pixels() {
if pixel[0] != pixel[1] || pixel[1] != pixel[2] {
return Ok(false);
}
}
Ok(true)
}
#[cfg(target_os = "linux")]
pub fn linux_elevator() -> String {
use std::process::Command;
@@ -176,39 +163,3 @@ macro_rules! t {
}
};
}
/// 将字节数转换为可读的流量字符串
/// 支持 B/s、KB/s、MB/s、GB/s 的自动转换
///
/// # Examples
/// ```not_run
/// format_bytes_speed(1000) // returns "1000B/s"
/// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s"
/// ```
/// ```
#[cfg(target_os = "macos")]
pub fn format_bytes_speed(speed: u64) -> String {
const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
let mut size = speed as f64;
let mut unit_index = 0;
while size >= 1000.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
format!("{:.1}{}/s", size, UNITS[unit_index])
}
#[cfg(target_os = "macos")]
#[test]
fn test_format_bytes_speed() {
assert_eq!(format_bytes_speed(0), "0.0B/s");
assert_eq!(format_bytes_speed(1023), "1.0KB/s");
assert_eq!(format_bytes_speed(1024), "1.0KB/s");
assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s");
assert_eq!(format_bytes_speed(1024 * 1024 * 1024), "1.0GB/s");
assert_eq!(format_bytes_speed(1024 * 500), "500.0KB/s");
assert_eq!(format_bytes_speed(1024 * 1024 * 2), "2.0MB/s");
}
@@ -13,6 +13,7 @@ use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
use percent_encoding::percent_decode_str;
use scopeguard;
use serde_yaml::Mapping;
use std::{
sync::Arc,
@@ -337,6 +338,12 @@ pub fn create_window(is_show: bool) -> bool {
*creating = (true, Instant::now());
// ScopeGuard 确保创建状态重置,防止 webview 卡死
let _guard = scopeguard::guard(creating, |mut creating_guard| {
*creating_guard = (false, Instant::now());
logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置");
});
match tauri::WebviewWindowBuilder::new(
&handle::Handle::global().app_handle().unwrap(),
"main", /* the unique window label */
@@ -419,8 +426,6 @@ pub fn create_window(is_show: bool) -> bool {
Ok(newly_created_window) => {
logging!(debug, Type::Window, true, "主窗口实例创建成功");
*creating = (false, Instant::now());
update_ui_ready_stage(UiReadyStage::NotStarted);
AsyncHandler::spawn(move || async move {
@@ -534,7 +539,6 @@ pub fn create_window(is_show: bool) -> bool {
}
Err(e) => {
logging!(error, Type::Window, true, "主窗口构建失败: {}", e);
*creating = (false, Instant::now()); // Reset the creating state if window creation failed
false
}
}
@@ -4,6 +4,14 @@ use tauri::{Manager, WebviewWindow, Wry};
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use scopeguard;
use std::{
sync::atomic::{AtomicBool, Ordering},
time::{Duration, Instant},
};
/// 窗口操作结果
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WindowOperationResult {
@@ -34,6 +42,45 @@ pub enum WindowState {
NotExist,
}
// 窗口操作防抖机制
static WINDOW_OPERATION_DEBOUNCE: OnceCell<Mutex<Instant>> = OnceCell::new();
static WINDOW_OPERATION_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
const WINDOW_OPERATION_DEBOUNCE_MS: u64 = 500;
fn get_window_operation_debounce() -> &'static Mutex<Instant> {
WINDOW_OPERATION_DEBOUNCE.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(1)))
}
fn should_handle_window_operation() -> bool {
if WINDOW_OPERATION_IN_PROGRESS.load(Ordering::Acquire) {
log::warn!(target: "app", "[防抖] 窗口操作已在进行中,跳过重复调用");
return false;
}
let debounce_lock = get_window_operation_debounce();
let mut last_operation = debounce_lock.lock();
let now = Instant::now();
let elapsed = now.duration_since(*last_operation);
log::debug!(target: "app", "[防抖] 检查窗口操作间隔: {}ms (需要>={}ms)",
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
if elapsed >= Duration::from_millis(WINDOW_OPERATION_DEBOUNCE_MS) {
*last_operation = now;
WINDOW_OPERATION_IN_PROGRESS.store(true, Ordering::Release);
log::info!(target: "app", "[防抖] 窗口操作被允许执行");
true
} else {
log::warn!(target: "app", "[防抖] 窗口操作被防抖机制忽略,距离上次操作 {}ms < {}ms",
elapsed.as_millis(), WINDOW_OPERATION_DEBOUNCE_MS);
false
}
}
fn finish_window_operation() {
WINDOW_OPERATION_IN_PROGRESS.store(false, Ordering::Release);
}
/// 统一的窗口管理器
pub struct WindowManager;
@@ -65,6 +112,14 @@ impl WindowManager {
/// 智能显示主窗口
pub fn show_main_window() -> WindowOperationResult {
// 防抖检查
if !should_handle_window_operation() {
return WindowOperationResult::NoAction;
}
let _guard = scopeguard::guard((), |_| {
finish_window_operation();
});
logging!(info, Type::Window, true, "开始智能显示主窗口");
logging!(
debug,
@@ -80,8 +135,11 @@ impl WindowManager {
WindowState::NotExist => {
logging!(info, Type::Window, true, "窗口不存在,创建新窗口");
if Self::create_new_window() {
logging!(info, Type::Window, true, "窗口创建成功");
std::thread::sleep(std::time::Duration::from_millis(100));
WindowOperationResult::Created
} else {
logging!(warn, Type::Window, true, "窗口创建失败");
WindowOperationResult::Failed
}
}
@@ -91,6 +149,16 @@ impl WindowManager {
}
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
if let Some(window) = Self::get_main_window() {
let state_after_check = Self::get_main_window_state();
if state_after_check == WindowState::VisibleFocused {
logging!(
info,
Type::Window,
true,
"窗口在检查期间已变为可见和有焦点状态"
);
return WindowOperationResult::NoAction;
}
Self::activate_window(&window)
} else {
WindowOperationResult::Failed
@@ -101,6 +169,14 @@ impl WindowManager {
/// 切换主窗口显示状态(显示/隐藏)
pub fn toggle_main_window() -> WindowOperationResult {
// 防抖检查
if !should_handle_window_operation() {
return WindowOperationResult::NoAction;
}
let _guard = scopeguard::guard((), |_| {
finish_window_operation();
});
logging!(info, Type::Window, true, "开始切换主窗口显示状态");
let current_state = Self::get_main_window_state();
@@ -108,37 +184,61 @@ impl WindowManager {
info,
Type::Window,
true,
"当前窗口状态: {:?}",
current_state
"当前窗口状态: {:?} | 详细状态: {}",
current_state,
Self::get_window_status_info()
);
match current_state {
WindowState::NotExist => {
// 窗口不存在,创建新窗口
logging!(info, Type::Window, true, "窗口不存在,将创建新窗口");
// 由于已经有防抖保护,直接调用内部方法
if Self::create_new_window() {
WindowOperationResult::Created
} else {
WindowOperationResult::Failed
}
}
WindowState::VisibleFocused => {
// 窗口可见且有焦点,隐藏它
if let Some(window) = Self::get_main_window() {
if window.hide().is_ok() {
logging!(info, Type::Window, true, "窗口已隐藏");
WindowOperationResult::Hidden
WindowState::VisibleFocused | WindowState::VisibleUnfocused => {
logging!(
info,
Type::Window,
true,
"窗口可见(焦点状态: {}),将隐藏窗口",
if current_state == WindowState::VisibleFocused {
"有焦点"
} else {
WindowOperationResult::Failed
"无焦点"
}
);
if let Some(window) = Self::get_main_window() {
match window.hide() {
Ok(_) => {
logging!(info, Type::Window, true, "窗口已成功隐藏");
WindowOperationResult::Hidden
}
Err(e) => {
logging!(warn, Type::Window, true, "隐藏窗口失败: {}", e);
WindowOperationResult::Failed
}
}
} else {
logging!(warn, Type::Window, true, "无法获取窗口实例");
WindowOperationResult::Failed
}
}
WindowState::VisibleUnfocused | WindowState::Minimized | WindowState::Hidden => {
// 窗口存在但不可见或无焦点,激活它
WindowState::Minimized | WindowState::Hidden => {
logging!(
info,
Type::Window,
true,
"窗口存在但被隐藏或最小化,将激活窗口"
);
if let Some(window) = Self::get_main_window() {
Self::activate_window(&window)
} else {
logging!(warn, Type::Window, true, "无法获取窗口实例");
WindowOperationResult::Failed
}
}
@@ -251,7 +351,7 @@ impl WindowManager {
.unwrap_or(false)
}
/// 创建新窗口现有的实现
/// 创建新窗口,防抖避免重复调用
fn create_new_window() -> bool {
use crate::utils::resolve;
resolve::create_window(true)
@@ -17,7 +17,7 @@ const formatUptime = (uptimeMs: number) => {
export const ClashInfoCard = () => {
const { t } = useTranslation();
const { version: clashVersion } = useClash();
const { clashConfig, sysproxy, rules, uptime } = useAppData();
const { clashConfig, rules, uptime, systemProxyAddress } = useAppData();
// 使用useMemo缓存格式化后的uptime,避免频繁计算
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]);
@@ -42,7 +42,7 @@ export const ClashInfoCard = () => {
{t("System Proxy Address")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{sysproxy?.server || "-"}
{systemProxyAddress}
</Typography>
</Stack>
<Divider />
@@ -74,7 +74,14 @@ export const ClashInfoCard = () => {
</Stack>
</Stack>
);
}, [clashConfig, clashVersion, t, formattedUptime, rules.length, sysproxy]);
}, [
clashConfig,
clashVersion,
t,
formattedUptime,
rules.length,
systemProxyAddress,
]);
return (
<EnhancedCard
@@ -1,5 +1,5 @@
import { useTranslation } from "react-i18next";
import { Box, Typography, Paper, Stack, Fade } from "@mui/material";
import { Box, Typography, Paper, Stack } from "@mui/material";
import { useLockFn } from "ahooks";
import { closeAllConnections } from "@/services/api";
import { patchClashMode } from "@/services/cmds";
@@ -26,7 +26,7 @@ import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
import { createAuthSockette } from "@/utils/websocket";
import parseTraffic from "@/utils/parse-traffic";
import { getConnections, isDebugEnabled, gc } from "@/services/api";
import { isDebugEnabled, gc } from "@/services/api";
import { ReactNode } from "react";
import { useAppData } from "@/providers/app-data-provider";
@@ -1,12 +1,5 @@
import { useTranslation } from "react-i18next";
import {
Box,
Typography,
Button,
Skeleton,
IconButton,
useTheme,
} from "@mui/material";
import { Box, Typography, Button, Skeleton, IconButton } from "@mui/material";
import {
LocationOnOutlined,
RefreshOutlined,
@@ -11,7 +11,7 @@ import { TrafficGraph, type TrafficRef } from "./traffic-graph";
import { useVisibility } from "@/hooks/use-visibility";
import parseTraffic from "@/utils/parse-traffic";
import useSWRSubscription from "swr/subscription";
import { createSockette, createAuthSockette } from "@/utils/websocket";
import { createAuthSockette } from "@/utils/websocket";
import { useTranslation } from "react-i18next";
import { isDebugEnabled, gc } from "@/services/api";
@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useLockFn } from "ahooks";
import yaml from "js-yaml";
import { useTranslation } from "react-i18next";
@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useLockFn } from "ahooks";
import yaml from "js-yaml";
import { useTranslation } from "react-i18next";
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useCallback } from "react";
import { useEffect, useMemo } from "react";
import { useVerge } from "@/hooks/use-verge";
import { filterSort } from "./use-filter-sort";
import { useWindowWidth } from "./use-window-width";
@@ -15,7 +15,7 @@ import {
Tooltip,
} from "@mui/material";
import { useLockFn } from "ahooks";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
@@ -209,7 +209,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
</GuardState>
</Item>
)}
{OS === "macos" && (
{/* {OS === "macos" && (
<Item>
<ListItemText primary={t("Enable Tray Speed")} />
<GuardState
@@ -223,7 +223,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
<Switch edge="end" />
</GuardState>
</Item>
)}
)} */}
{OS === "macos" && (
<Item>
<ListItemText primary={t("Enable Tray Icon")} />
@@ -3,6 +3,7 @@ import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-provider";
import { getClashConfig } from "@/services/api";
import {
getAutotemProxy,
@@ -172,6 +173,35 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
}
};
const { systemProxyAddress } = useAppData();
// 为当前状态计算系统代理地址
const getSystemProxyAddress = useMemo(() => {
if (!clashConfig) return "-";
const isPacMode = value.pac ?? false;
if (isPacMode) {
const host = value.proxy_host || "127.0.0.1";
const port = verge?.verge_mixed_port || clashConfig["mixed-port"] || 7897;
return `${host}:${port}`;
} else {
return systemProxyAddress;
}
}, [
value.pac,
value.proxy_host,
verge?.verge_mixed_port,
clashConfig,
systemProxyAddress,
]);
const getCurrentPacUrl = useMemo(() => {
const host = value.proxy_host || "127.0.0.1";
// 根据环境判断PAC端口
const port = import.meta.env.DEV ? 11233 : 33331;
return `http://${host}:${port}/commands/pac`;
}, [value.proxy_host]);
useImperativeHandle(ref, () => ({
open: () => {
setOpen(true);
@@ -417,7 +447,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
<FlexBox>
<Typography className="label">{t("Server Addr")}</Typography>
<Typography className="value">
{sysproxy?.server ? sysproxy.server : t("Not available")}
{getSystemProxyAddress}
</Typography>
</FlexBox>
</>
@@ -425,7 +455,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
{value.pac && (
<FlexBox>
<Typography className="label">{t("PAC URL")}</Typography>
<Typography className="value">{autoproxy?.url || "-"}</Typography>
<Typography className="value">
{getCurrentPacUrl || "-"}
</Typography>
</FlexBox>
)}
</BaseFieldset>
@@ -1,5 +1,5 @@
import useSWR, { mutate } from "swr";
import { useRef, useEffect, useState } from "react";
import { useRef } from "react";
import { useTranslation } from "react-i18next";
import {
SettingsRounded,
@@ -19,7 +19,6 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import {
getSystemProxy,
getAutotemProxy,
installService,
uninstallService,
restartCore,
stopCore,
@@ -24,11 +24,7 @@ import {
getSystemProxy,
getAutotemProxy,
getRunningMode,
installService,
restartCore,
isServiceAvailable,
} from "@/services/cmds";
import { useLockFn } from "ahooks";
import { closeAllConnections } from "@/services/api";
import { showNotice } from "@/services/noticeService";
import { useServiceInstaller } from "@/hooks/useServiceInstaller";
@@ -1,10 +1,4 @@
import { useEffect } from "react";
import { useEnableLog } from "../services/states";
import { createSockette, createAuthSockette } from "../utils/websocket";
import { useClashInfo } from "./use-clash";
import dayjs from "dayjs";
import { create } from "zustand";
import { useVisibility } from "./use-visibility";
import {
useGlobalLogData,
clearGlobalLogs,
-1
View File
@@ -21,7 +21,6 @@ import { useCustomTheme } from "@/components/layout/use-custom-theme";
import getSystem from "@/utils/get-system";
import "dayjs/locale/ru";
import "dayjs/locale/zh-cn";
import { getPortableFlag } from "@/services/cmds";
import React from "react";
import { useListen } from "@/hooks/use-listen";
import { listen } from "@tauri-apps/api/event";
+1 -4
View File
@@ -19,10 +19,7 @@ import {
ConnectionDetailRef,
} from "@/components/connection/connection-detail";
import parseTraffic from "@/utils/parse-traffic";
import {
BaseSearchBox,
type SearchState,
} from "@/components/base/base-search-box";
import { BaseSearchBox } from "@/components/base/base-search-box";
import { BaseStyledSelect } from "@/components/base/base-styled-select";
import { useTheme } from "@mui/material/styles";
import { useVisibility } from "@/hooks/use-visibility";
+1 -5
View File
@@ -37,11 +37,7 @@ import { BasePage } from "@/components/base";
import { ClashInfoCard } from "@/components/home/clash-info-card";
import { SystemInfoCard } from "@/components/home/system-info-card";
import { useLockFn } from "ahooks";
import {
entry_lightweight_mode,
openWebUrl,
patchVergeConfig,
} from "@/services/cmds";
import { entry_lightweight_mode, openWebUrl } from "@/services/cmds";
import { TestCard } from "@/components/home/test-card";
import { IpInfoCard } from "@/components/home/ip-info-card";
+1 -1
View File
@@ -45,7 +45,7 @@ import { ProfileMore } from "@/components/profile/profile-more";
import { ProfileItem } from "@/components/profile/profile-item";
import { useProfiles } from "@/hooks/use-profiles";
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
import { add, throttle } from "lodash-es";
import { throttle } from "lodash-es";
import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
import { readTextFile } from "@tauri-apps/plugin-fs";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
@@ -1,4 +1,5 @@
import { createContext, useContext, useMemo, useEffect } from "react";
import React, { createContext, useContext, useEffect, useMemo } from "react";
import { useVerge } from "@/hooks/use-verge";
import useSWR from "swr";
import useSWRSubscription from "swr/subscription";
import {
@@ -37,6 +38,8 @@ interface AppDataContextType {
};
traffic: { up: number; down: number };
memory: { inuse: number };
systemProxyAddress: string;
refreshProxy: () => Promise<any>;
refreshClashConfig: () => Promise<any>;
refreshRules: () => Promise<any>;
@@ -55,8 +58,9 @@ export const AppDataProvider = ({
}: {
children: React.ReactNode;
}) => {
const { clashInfo } = useClashInfo();
const pageVisible = useVisibility();
const { clashInfo } = useClashInfo();
const { verge } = useVerge();
// 基础数据 - 中频率更新 (5秒)
const { data: proxiesData, mutate: refreshProxy } = useSWR(
@@ -508,8 +512,39 @@ export const AppDataProvider = ({
};
// 聚合所有数据
const value = useMemo(
() => ({
const value = useMemo(() => {
// 计算系统代理地址
const calculateSystemProxyAddress = () => {
if (!verge || !clashConfig) return "-";
const isPacMode = verge.proxy_auto_config ?? false;
if (isPacMode) {
// PAC模式:显示我们期望设置的代理地址
const proxyHost = verge.proxy_host || "127.0.0.1";
const proxyPort =
verge.verge_mixed_port || clashConfig["mixed-port"] || 7897;
return `${proxyHost}:${proxyPort}`;
} else {
// HTTP代理模式:优先使用系统地址,但如果格式不正确则使用期望地址
const systemServer = sysproxy?.server;
if (
systemServer &&
systemServer !== "-" &&
!systemServer.startsWith(":")
) {
return systemServer;
} else {
// 系统地址无效,返回期望的代理地址
const proxyHost = verge.proxy_host || "127.0.0.1";
const proxyPort =
verge.verge_mixed_port || clashConfig["mixed-port"] || 7897;
return `${proxyHost}:${proxyPort}`;
}
}
};
return {
// 数据
proxies: proxiesData,
clashConfig,
@@ -534,6 +569,8 @@ export const AppDataProvider = ({
traffic: trafficData,
memory: memoryData,
systemProxyAddress: calculateSystemProxyAddress(),
// 刷新方法
refreshProxy,
refreshClashConfig,
@@ -542,27 +579,27 @@ export const AppDataProvider = ({
refreshProxyProviders,
refreshRuleProviders,
refreshAll,
}),
[
proxiesData,
clashConfig,
rulesData,
sysproxy,
runningMode,
uptimeData,
connectionsData,
trafficData,
memoryData,
proxyProviders,
ruleProviders,
refreshProxy,
refreshClashConfig,
refreshRules,
refreshSysproxy,
refreshProxyProviders,
refreshRuleProviders,
],
);
};
}, [
proxiesData,
clashConfig,
rulesData,
sysproxy,
runningMode,
uptimeData,
connectionsData,
trafficData,
memoryData,
proxyProviders,
ruleProviders,
verge,
refreshProxy,
refreshClashConfig,
refreshRules,
refreshSysproxy,
refreshProxyProviders,
refreshRuleProviders,
]);
return (
<AppDataContext.Provider value={value}>{children}</AppDataContext.Provider>
-1
View File
@@ -1,7 +1,6 @@
import axios, { AxiosInstance } from "axios";
import { getClashInfo } from "./cmds";
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
let instancePromise: Promise<AxiosInstance> = null!;
@@ -1,8 +1,7 @@
// 全局日志服务,使应用在任何页面都能收集日志
import { create } from "zustand";
import { createSockette, createAuthSockette } from "@/utils/websocket";
import { createAuthSockette } from "@/utils/websocket";
import dayjs from "dayjs";
import { useState, useEffect } from "react";
// 最大日志数量
const MAX_LOG_NUM = 1000;
+1 -1
View File
@@ -730,7 +730,7 @@ function URI_Trojan(line: string): IProxyTrojanConfig {
function URI_Hysteria2(line: string): IProxyHysteria2Config {
line = line.split(/(hysteria2|hy2):\/\//)[2];
// eslint-disable-next-line no-unused-vars
let [__, password, server, ___, port, ____, addons = "", name] =
/^(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line) || [];
let portNum = parseInt(`${port}`, 10);
+15
View File
@@ -2,6 +2,21 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [2.33.4](https://github.com/filebrowser/filebrowser/compare/v2.33.3...v2.33.4) (2025-06-22)
### Features
* translation updates for project File Browser ([#5179](https://github.com/filebrowser/filebrowser/issues/5179)) ([f714e71](https://github.com/filebrowser/filebrowser/commit/f714e71a356c2301f394d651c9b6c467440508e3))
### [2.33.3](https://github.com/filebrowser/filebrowser/compare/v2.33.2...v2.33.3) (2025-06-22)
### Bug Fixes
* keep command behavior in Dockerfile ([7c0c782](https://github.com/filebrowser/filebrowser/commit/7c0c7820efbbed2f0499353cc76ecb85d00ff7c3))
* update search hotkey in help prompt ([#5178](https://github.com/filebrowser/filebrowser/issues/5178)) ([2741616](https://github.com/filebrowser/filebrowser/commit/2741616473636d40b7e9f14c9906ada08d328c3c))
### [2.33.2](https://github.com/filebrowser/filebrowser/compare/v2.33.1...v2.33.2) (2025-06-21)
+1 -2
View File
@@ -29,5 +29,4 @@ VOLUME /srv /config /database
EXPOSE 80
ENTRYPOINT [ "tini", "--", "/init.sh" ]
CMD [ "filebrowser", "--config", "/config/settings.json" ]
ENTRYPOINT [ "tini", "--", "/init.sh", "filebrowser", "--config", "/config/settings.json" ]
@@ -11,7 +11,7 @@
<li><strong>DEL</strong> - {{ $t("help.del") }}</li>
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li>
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li>
<li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li>
<li><strong>CTRL + SHIFT + F</strong> - {{ $t("help.ctrl.f") }}</li>
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li>
<li><strong>Click</strong> - {{ $t("help.click") }}</li>
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li>
+17 -2
View File
@@ -3,14 +3,17 @@
"cancel": "Abbrechen",
"clear": "Schließen",
"close": "Schließen",
"continue": "Fortfahren",
"copy": "Kopieren",
"copyFile": "Kopiere Datei",
"copyToClipboard": "In Zwischenablage kopieren",
"copyDownloadLinkToClipboard": "Download-Link in die Zwischenablage kopieren",
"create": "Neu",
"delete": "Löschen",
"download": "Herunterladen",
"file": "Datei",
"folder": "Ordner",
"fullScreen": "Vollbildmodus umschalten",
"hideDotfiles": "Versteckte Dateien ausblenden",
"info": "Info",
"more": "mehr",
@@ -21,6 +24,7 @@
"ok": "OK",
"permalink": "permanenten Verweis anzeigen",
"previous": "vorherige",
"preview": "Vorschau",
"publish": "Veröffentlichen",
"rename": "umbenennen",
"replace": "Ersetzen",
@@ -37,13 +41,17 @@
"toggleSidebar": "Seitenleiste anzeigen",
"update": "Update",
"upload": "Upload",
"openFile": "Datei öffnen"
"openFile": "Datei öffnen",
"discardChanges": "Verwerfen"
},
"download": {
"downloadFile": "Download Datei",
"downloadFolder": "Download Ordner",
"downloadSelected": "Auswahl herunterladen"
},
"upload": {
"abortUpload": "Sind Sie sicher, dass Sie den Vorgang abbrechen möchten?"
},
"errors": {
"forbidden": "Sie haben keine Berechtigung dies abzurufen.",
"internal": "Etwas ist schiefgelaufen.",
@@ -102,6 +110,7 @@
"deleteMessageMultiple": "Sind Sie sicher, dass Sie {count} Datei(en) löschen möchten?",
"deleteMessageSingle": "Sind Sie sicher, dass Sie diesen Ordner/diese Datei löschen möchten?",
"deleteMessageShare": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten ({path})?",
"deleteUser": "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?",
"deleteTitle": "Lösche Dateien",
"displayName": "Anzeigename:",
"download": "Lade Dateien",
@@ -130,7 +139,9 @@
"upload": "Upload",
"uploadFiles": "Upload von {files} Dateien...",
"uploadMessage": "Wählen Sie eine Upload-Methode",
"optionalPassword": "Optionales Passwort"
"optionalPassword": "Optionales Passwort",
"resolution": "Auflösung",
"discardEditorChanges": "Möchten Sie die vorgenommenen Änderungen wirklich verwerfen?"
},
"search": {
"images": "Bilder",
@@ -163,6 +174,9 @@
"tusUploadsHelp": "File Browser unterstützt das Hochladen von gestückelten Dateien und ermöglicht so einen effizienten, zuverlässigen, fortsetzbaren und gestückelten Datei-Upload auch in unzuverlässigen Netzwerken.",
"tusUploadsChunkSize": "Gibt die maximale Größe pro Anfrage an (direkte Uploads werden für kleinere Uploads verwendet). Bitte geben Sie eine Byte-Angabe oder eine Zeichenfolge wie 10 MB, 1 GB usw. an",
"tusUploadsRetryCount": "Anzahl der Wiederholungsversuche, wenn das Hochladen eines Stückes fehlschlägt.",
"userHomeBasePath": "Basispfad für Benutzer-Home-Verzeichnisse",
"userScopeGenerationPlaceholder": "Scope wird automatisch generiert",
"createUserHomeDirectory": "Benutzer-Home-Verzeichnis erstellen",
"customStylesheet": "Individuelles Stylesheet",
"defaultUserDescription": "Das sind die Standardeinstellung für Benutzer",
"disableExternalLinks": "Externe Links deaktivieren (außer Dokumentation)",
@@ -209,6 +223,7 @@
"shareDeleted": "Freigabe gelöscht!",
"singleClick": "Einfacher Klick zum Öffnen von Dateien und Ordnern",
"themes": {
"default": "Systemstandard",
"dark": "Dunkel",
"light": "Hell",
"title": "Erscheinungsbild"
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=filebrowser
PKG_VERSION:=2.33.2
PKG_VERSION:=2.33.4
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/filebrowser/filebrowser/tar.gz/v${PKG_VERSION}?
PKG_HASH:=c0af0ce2ebed89b2f31cda15b7a8f200384a52ceedc2c7f7fae99abf1d00e4c9
PKG_HASH:=959666db372311958f4223ef52f2906acce8b1d7125aae58050a2e99f4e8140a
PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=LICENSE
+1 -1
View File
@@ -1,4 +1,4 @@
/target
target
/build/release
/build/target
/build/install
+94 -87
View File
@@ -219,7 +219,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -230,9 +230,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "backtrace"
@@ -284,7 +284,7 @@ dependencies = [
"regex",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -302,7 +302,7 @@ dependencies = [
"regex",
"rustc-hash 2.1.1",
"shlex",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -428,7 +428,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -467,18 +467,18 @@ dependencies = [
[[package]]
name = "c2rust-bitfields"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "367e5d1b30f28be590b6b3868da1578361d29d9bfac516d22f497d28ed7c9055"
checksum = "46dc7d2bffa0d0b3d47eb2dc69973466858281446c2ac9f6d8a10e92ab1017df"
dependencies = [
"c2rust-bitfields-derive",
]
[[package]]
name = "c2rust-bitfields-derive"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a279db9c50c4024eeca1a763b6e0f033848ce74e83e47454bcf8a8a98f7b0b56"
checksum = "ebe1117afa5937ce280034e31fa1e84ed1824a252f75380327eed438535333f8"
dependencies = [
"proc-macro2",
"quote",
@@ -497,9 +497,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.26"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"jobserver",
"libc",
@@ -811,7 +811,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -888,7 +888,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -899,7 +899,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -920,7 +920,7 @@ checksum = "7a4102713839a8c01c77c165bc38ef2e83948f6397fa1e1dcfacec0f07b149d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -986,7 +986,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -1020,12 +1020,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1207,7 +1207,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -1881,9 +1881,9 @@ dependencies = [
[[package]]
name = "jiff"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
@@ -1894,13 +1894,13 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -1979,7 +1979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.53.2",
]
[[package]]
@@ -2373,7 +2373,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -2483,9 +2483,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [
"memchr",
"thiserror 2.0.12",
@@ -2494,9 +2494,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
dependencies = [
"pest",
"pest_generator",
@@ -2504,24 +2504,23 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
name = "pest_meta"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
dependencies = [
"once_cell",
"pest",
"sha2",
]
@@ -2543,7 +2542,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -2675,7 +2674,7 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -2737,9 +2736,9 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.12"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842"
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
dependencies = [
"cfg_aliases",
"libc",
@@ -2760,9 +2759,9 @@ dependencies = [
[[package]]
name = "r-efi"
version = "5.2.0"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "radium"
@@ -2831,9 +2830,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.12"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
"bitflags 2.9.1",
]
@@ -3072,9 +3071,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.27"
version = "0.23.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
dependencies = [
"log",
"once_cell",
@@ -3160,6 +3159,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sealed"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "sec1"
version = "0.7.3"
@@ -3261,7 +3271,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -3347,6 +3357,7 @@ dependencies = [
"percent-encoding",
"pin-project",
"rand 0.9.1",
"sealed",
"sendfd",
"serde",
"serde_json",
@@ -3523,12 +3534,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
[[package]]
name = "sm4"
@@ -3638,9 +3646,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.102"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@@ -3664,7 +3672,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -3770,7 +3778,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -3781,7 +3789,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -3796,12 +3804,11 @@ dependencies = [
[[package]]
name = "thread_local"
version = "1.1.8"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
@@ -3888,7 +3895,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -4000,13 +4007,13 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.29"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -4057,7 +4064,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -4255,7 +4262,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
"wasm-bindgen-shared",
]
@@ -4290,7 +4297,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4370,7 +4377,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -4381,9 +4388,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.1"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core",
@@ -4433,7 +4440,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -4444,14 +4451,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
name = "windows-link"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-numerics"
@@ -4755,9 +4762,9 @@ dependencies = [
[[package]]
name = "wintun-bindings"
version = "0.7.31"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "605f50b13e12e1f9f99dc5e93701d779dbe47282fec186cb8a079165368d3124"
checksum = "b88303b411e20a1319b368dcd04db1480003ed46ac35193e139f542720b15fbf"
dependencies = [
"blocking",
"c2rust-bitfields",
@@ -4765,7 +4772,7 @@ dependencies = [
"libloading",
"log",
"thiserror 2.0.12",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
"winreg 0.55.0",
]
@@ -4819,28 +4826,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -4860,7 +4867,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
"synstructure",
]
@@ -4900,7 +4907,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.102",
"syn 2.0.104",
]
[[package]]
@@ -72,6 +72,7 @@ percent-encoding = "2.1"
futures = "0.3"
trait-variant = "0.1"
dynosaur = "0.2.0"
sealed = "0.6"
socket2 = { version = "0.5", features = ["all"] }
tokio = { version = "1.9.0", features = [
@@ -2,8 +2,6 @@
use std::net::SocketAddr;
#[cfg(target_os = "android")]
pub use self::option::android::{SocketProtect, SocketProtectFn};
#[cfg(unix)]
pub use self::sys::uds::{UnixListener, UnixStream};
pub use self::{
@@ -59,6 +59,9 @@ pub struct ConnectOpts {
/// This is an [Android shadowsocks implementation](https://github.com/shadowsocks/shadowsocks-android) specific feature
#[cfg(target_os = "android")]
pub vpn_protect_path: Option<std::path::PathBuf>,
/// A customizable socket protect implementation for Android for calling `VpnService.protect(fd)`
///
/// see [`ConnectOpts::set_vpn_socket_protect`]
#[cfg(target_os = "android")]
pub vpn_socket_protect: Option<std::sync::Arc<Box<dyn android::SocketProtect + Send + Sync>>>,
@@ -93,67 +96,77 @@ pub struct AcceptOpts {
#[cfg(target_os = "android")]
impl ConnectOpts {
/// Set `vpn_protect_path` for Android VPNService.protect implementation
///
/// Example:
///
/// ```rust
/// // Sync function for calling `VpnService.protect(fd)`
/// opts.set_vpn_socket_protect(|fd| {
/// // Your implementation here
/// // For example, using `jni` to call Android's VpnService.protect(fd)
/// Ok(())
/// });
/// ```
pub fn set_vpn_socket_protect<F>(&mut self, f: F)
where
F: self::android::MakeSocketProtect + Send + Sync + 'static,
F: android::MakeSocketProtect + Send + Sync + 'static,
F::SocketProtectType: android::SocketProtect + Send + Sync + 'static,
{
let protect_fn = Box::new(f.make_socket_protect()) as Box<dyn android::SocketProtect + Send + Sync>;
self.vpn_socket_protect = Some(std::sync::Arc::new(protect_fn))
self.vpn_socket_protect = Some(std::sync::Arc::new(Box::new(f.make_socket_protect())));
}
}
/// Android specific features
#[cfg(target_os = "android")]
pub mod android {
use std::{fmt, io, os::unix::io::RawFd, sync::Arc};
use sealed::sealed;
use std::{fmt, io, os::unix::io::RawFd};
/// Android VPN socket protect implemetation
#[sealed]
pub trait SocketProtect {
/// Protects the socket file descriptor by calling `VpnService.protect(fd)`
fn protect(&self, fd: RawFd) -> io::Result<()>;
}
impl fmt::Debug for dyn SocketProtect + Send + Sync {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SocketProtect").finish_non_exhaustive()
}
}
/// Creating an instance of `SocketProtect`
#[sealed]
pub trait MakeSocketProtect {
type SocketProtect: SocketProtect + Send + Sync;
type SocketProtectType: SocketProtect;
/// Creates an instance of `SocketProtect`
fn make_socket_protect(self) -> Self::SocketProtect;
fn make_socket_protect(self) -> Self::SocketProtectType;
}
/// A function that implements `SocketProtect` trait
pub struct SocketProtectFn<F> {
pub f: F,
f: F,
}
#[sealed]
impl<F> SocketProtect for SocketProtectFn<F>
where
F: Fn(RawFd) -> io::Result<()>,
F: Fn(RawFd) -> io::Result<()> + Send + Sync + 'static,
{
fn protect(&self, fd: RawFd) -> io::Result<()> {
(self.f)(fd)
}
}
impl<F> Clone for SocketProtectFn<Arc<F>> {
fn clone(&self) -> Self {
Self { f: self.f.clone() }
}
}
impl fmt::Debug for dyn SocketProtect + Send + Sync {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("dyn SocketProtect + Send + Sync").finish()
}
}
#[sealed]
impl<F> MakeSocketProtect for F
where
F: Fn(RawFd) -> io::Result<()> + Send + Sync + 'static,
{
type SocketProtect = SocketProtectFn<F>;
type SocketProtectType = SocketProtectFn<F>;
fn make_socket_protect(self) -> Self::SocketProtect {
fn make_socket_protect(self) -> Self::SocketProtectType {
SocketProtectFn { f: self }
}
}
@@ -9,7 +9,6 @@ use std::{
task::{self, Poll},
};
use cfg_if::cfg_if;
use log::{debug, error, warn};
use pin_project::pin_project;
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
@@ -56,22 +55,7 @@ impl TcpStream {
// This is a workaround for VPNService
#[cfg(target_os = "android")]
if !addr.ip().is_loopback() {
use std::time::Duration;
use tokio::time;
if let Some(ref path) = opts.vpn_protect_path {
// RPC calls to `VpnService.protect()`
// Timeout in 3 seconds like shadowsocks-libev
match time::timeout(Duration::from_secs(3), vpn_protect(path, socket.as_raw_fd())).await {
Ok(Ok(..)) => {}
Ok(Err(err)) => return Err(err),
Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, "protect() timeout")),
}
}
if let Some(ref protect) = opts.vpn_socket_protect {
protect.protect(socket.as_raw_fd())?;
}
android::vpn_protect(&socket, opts).await?;
}
// Set SO_MARK for mark-based routing on Linux (since 2.6.25)
@@ -335,24 +319,7 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp
// Any traffic except localhost should be protected
// This is a workaround for VPNService
#[cfg(target_os = "android")]
{
use std::time::Duration;
use tokio::time;
if let Some(ref path) = config.vpn_protect_path {
// RPC calls to `VpnService.protect()`
// Timeout in 3 seconds like shadowsocks-libev
match time::timeout(Duration::from_secs(3), vpn_protect(path, socket.as_raw_fd())).await {
Ok(Ok(..)) => {}
Ok(Err(err)) => return Err(err),
Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, "protect() timeout")),
}
}
if let Some(ref protect) = config.vpn_socket_protect {
protect.protect(socket.as_raw_fd())?;
}
}
android::vpn_protect(&socket, config).await?;
// Set SO_MARK for mark-based routing on Linux (since 2.6.25)
// NOTE: This will require CAP_NET_ADMIN capability (root in most cases)
@@ -403,36 +370,67 @@ fn set_bindtodevice<S: AsRawFd>(socket: &S, iface: &str) -> io::Result<()> {
Ok(())
}
cfg_if! {
if #[cfg(target_os = "android")] {
use std::path::Path;
use tokio::io::AsyncReadExt;
#[cfg(target_os = "android")]
mod android {
use std::{
io::{self, ErrorKind},
os::unix::io::{AsRawFd, RawFd},
path::Path,
time::Duration,
};
use tokio::{io::AsyncReadExt, time};
use super::uds::UnixStream;
use super::super::uds::UnixStream;
use super::ConnectOpts;
/// This is a RPC for Android to `protect()` socket for connecting to remote servers
///
/// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
///
/// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project.
async fn vpn_protect<P: AsRef<Path>>(protect_path: P, fd: RawFd) -> io::Result<()> {
let mut stream = UnixStream::connect(protect_path).await?;
/// This is a RPC for Android to `protect()` socket for connecting to remote servers
///
/// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
///
/// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project.
async fn send_vpn_protect_uds<P: AsRef<Path>>(protect_path: P, fd: RawFd) -> io::Result<()> {
let mut stream = UnixStream::connect(protect_path).await?;
// send fds
let dummy: [u8; 1] = [1];
let fds: [RawFd; 1] = [fd];
stream.send_with_fd(&dummy, &fds).await?;
// send fds
let dummy: [u8; 1] = [1];
let fds: [RawFd; 1] = [fd];
stream.send_with_fd(&dummy, &fds).await?;
// receive the return value
let mut response = [0; 1];
stream.read_exact(&mut response).await?;
// receive the return value
let mut response = [0; 1];
stream.read_exact(&mut response).await?;
if response[0] == 0xFF {
return Err(io::Error::other("protect() failed"));
}
Ok(())
if response[0] == 0xFF {
return Err(io::Error::other("protect() failed"));
}
Ok(())
}
/// Try to run VPNService#protect on Android
///
/// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
pub async fn vpn_protect<S>(socket: &S, opts: &ConnectOpts) -> io::Result<()>
where
S: AsRawFd + Send + Sync + 'static,
{
// shadowsocks-android uses a Unix domain socket to communicate with the VPNService#protect
if let Some(ref path) = opts.vpn_protect_path {
// RPC calls to `VpnService.protect()`
// Timeout in 3 seconds like shadowsocks-libev
match time::timeout(Duration::from_secs(3), send_vpn_protect_uds(path, socket.as_raw_fd())).await {
Ok(Ok(..)) => {}
Ok(Err(err)) => return Err(err),
Err(..) => return Err(io::Error::new(ErrorKind::TimedOut, "protect() timeout")),
}
}
// Customized SocketProtect
if let Some(ref protect) = opts.vpn_socket_protect {
protect.protect(socket.as_raw_fd())?;
}
Ok(())
}
}
+11 -3
View File
@@ -22,10 +22,18 @@ jobs:
$github = Invoke-RestMethod -uri "https://api.github.com/repos/2dust/v2rayN/releases"
$targetRelease = $github | Where-Object -Property prerelease -match 'False' | Select -First 1
$installerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip*' | Select -ExpandProperty browser_download_url
$x64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-64\.zip' | Select -ExpandProperty browser_download_url
$arm64InstallerUrl = $targetRelease | Select -ExpandProperty assets -First 1 | Where-Object -Property name -match 'v2rayN-windows-arm64\.zip' | Select -ExpandProperty browser_download_url
$ver = $targetRelease.tag_name
# getting latest wingetcreate file
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
.\wingetcreate.exe update $wingetPackage -s -v $ver -u "$installerUrl|x64" -t $gitToken
Write-Host "Updating with both x64 and arm64 installers"
Write-Host "Version: $ver"
Write-Host "x64 URL: $x64InstallerUrl"
Write-Host "arm64 URL: $arm64InstallerUrl"
.\wingetcreate.exe update $wingetPackage -s -v $ver -u "$x64InstallerUrl|x64" "$arm64InstallerUrl|arm64" -t $gitToken
@@ -105,6 +105,7 @@ public class UIItem
public bool Hide2TrayWhenClose { get; set; }
public List<ColumnItem> MainColumnItem { get; set; }
public bool ShowInTaskbar { get; set; }
public bool MacOSShowInDock { get; set; }
}
[Serializable]
@@ -11,11 +11,6 @@ public partial class App : Application
{
public override void Initialize()
{
if (!AppHandler.Instance.InitApp())
{
Environment.Exit(0);
return;
}
AvaloniaXamlLoader.Load(this);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+27 -13
View File
@@ -14,13 +14,17 @@ internal class Program
[STAThread]
public static void Main(string[] args)
{
OnStartup(args);
if (OnStartup(args) == false)
{
Environment.Exit(0);
return;
}
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
private static void OnStartup(string[]? Args)
private static bool OnStartup(string[]? Args)
{
if (Utils.IsWindows())
{
@@ -30,8 +34,7 @@ internal class Program
if (!rebootas && !bCreatedNew)
{
ProgramStarted.Set();
Environment.Exit(0);
return;
return false;
}
}
else
@@ -39,19 +42,30 @@ internal class Program
_ = new Mutex(true, "v2rayN", out var bOnlyOneInstance);
if (!bOnlyOneInstance)
{
Environment.Exit(0);
return;
return false;
}
}
if (!AppHandler.Instance.InitApp())
{
return false;
}
return true;
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
//.WithInterFont()
.WithFontByDefault()
.LogToTrace()
.UseReactiveUI()
.With(new MacOSPlatformOptions { ShowInDock = false });
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
//.WithInterFont()
.WithFontByDefault()
.LogToTrace()
#if OS_OSX
.UseReactiveUI()
.With(new MacOSPlatformOptions { ShowInDock = AppHandler.Instance.Config.UiItem.MacOSShowInDock });
#else
.UseReactiveUI();
#endif
}
}